1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Counter Watch Events - Test various counter watch events in a userspace application |
4 | * |
5 | * Copyright (C) STMicroelectronics 2023 - All Rights Reserved |
6 | * Author: Fabrice Gasnier <fabrice.gasnier@foss.st.com>. |
7 | */ |
8 | |
9 | #include <errno.h> |
10 | #include <fcntl.h> |
11 | #include <getopt.h> |
12 | #include <linux/counter.h> |
13 | #include <linux/kernel.h> |
14 | #include <stdlib.h> |
15 | #include <stdio.h> |
16 | #include <string.h> |
17 | #include <sys/ioctl.h> |
18 | #include <unistd.h> |
19 | |
20 | static struct counter_watch simple_watch[] = { |
21 | { |
22 | /* Component data: Count 0 count */ |
23 | .component.type = COUNTER_COMPONENT_COUNT, |
24 | .component.scope = COUNTER_SCOPE_COUNT, |
25 | .component.parent = 0, |
26 | /* Event type: overflow or underflow */ |
27 | .event = COUNTER_EVENT_OVERFLOW_UNDERFLOW, |
28 | /* Device event channel 0 */ |
29 | .channel = 0, |
30 | }, |
31 | }; |
32 | |
33 | static const char * const counter_event_type_name[] = { |
34 | "COUNTER_EVENT_OVERFLOW" , |
35 | "COUNTER_EVENT_UNDERFLOW" , |
36 | "COUNTER_EVENT_OVERFLOW_UNDERFLOW" , |
37 | "COUNTER_EVENT_THRESHOLD" , |
38 | "COUNTER_EVENT_INDEX" , |
39 | "COUNTER_EVENT_CHANGE_OF_STATE" , |
40 | "COUNTER_EVENT_CAPTURE" , |
41 | }; |
42 | |
43 | static const char * const counter_component_type_name[] = { |
44 | "COUNTER_COMPONENT_NONE" , |
45 | "COUNTER_COMPONENT_SIGNAL" , |
46 | "COUNTER_COMPONENT_COUNT" , |
47 | "COUNTER_COMPONENT_FUNCTION" , |
48 | "COUNTER_COMPONENT_SYNAPSE_ACTION" , |
49 | "COUNTER_COMPONENT_EXTENSION" , |
50 | }; |
51 | |
52 | static const char * const counter_scope_name[] = { |
53 | "COUNTER_SCOPE_DEVICE" , |
54 | "COUNTER_SCOPE_SIGNAL" , |
55 | "COUNTER_SCOPE_COUNT" , |
56 | }; |
57 | |
58 | static void print_watch(struct counter_watch *watch, int nwatch) |
59 | { |
60 | int i; |
61 | |
62 | /* prints the watch array in C-like structure */ |
63 | printf("watch[%d] = {\n" , nwatch); |
64 | for (i = 0; i < nwatch; i++) { |
65 | printf(" [%d] =\t{\n" |
66 | "\t\t.component.type = %s\n" |
67 | "\t\t.component.scope = %s\n" |
68 | "\t\t.component.parent = %d\n" |
69 | "\t\t.component.id = %d\n" |
70 | "\t\t.event = %s\n" |
71 | "\t\t.channel = %d\n" |
72 | "\t},\n" , |
73 | i, |
74 | counter_component_type_name[watch[i].component.type], |
75 | counter_scope_name[watch[i].component.scope], |
76 | watch[i].component.parent, |
77 | watch[i].component.id, |
78 | counter_event_type_name[watch[i].event], |
79 | watch[i].channel); |
80 | } |
81 | printf("};\n" ); |
82 | } |
83 | |
84 | static void print_usage(void) |
85 | { |
86 | fprintf(stderr, "Usage:\n\n" |
87 | "counter_watch_events [options] [-w <watchoptions>]\n" |
88 | "counter_watch_events [options] [-w <watch1 options>] [-w <watch2 options>]...\n" |
89 | "\n" |
90 | "When no --watch option has been provided, simple watch example is used:\n" |
91 | "counter_watch_events [options] -w comp_count,scope_count,evt_ovf_udf\n" |
92 | "\n" |
93 | "Test various watch events for given counter device.\n" |
94 | "\n" |
95 | "Options:\n" |
96 | " -d, --debug Prints debug information\n" |
97 | " -h, --help Prints usage\n" |
98 | " -n, --device-num <n> Use /dev/counter<n> [default: /dev/counter0]\n" |
99 | " -l, --loop <n> Loop for <n> events [default: 0 (forever)]\n" |
100 | " -w, --watch <watchoptions> comma-separated list of watch options\n" |
101 | "\n" |
102 | "Watch options:\n" |
103 | " scope_device (COUNTER_SCOPE_DEVICE) [default: scope_device]\n" |
104 | " scope_signal (COUNTER_SCOPE_SIGNAL)\n" |
105 | " scope_count (COUNTER_SCOPE_COUNT)\n" |
106 | "\n" |
107 | " comp_none (COUNTER_COMPONENT_NONE) [default: comp_none]\n" |
108 | " comp_signal (COUNTER_COMPONENT_SIGNAL)\n" |
109 | " comp_count (COUNTER_COMPONENT_COUNT)\n" |
110 | " comp_function (COUNTER_COMPONENT_FUNCTION)\n" |
111 | " comp_synapse_action (COUNTER_COMPONENT_SYNAPSE_ACTION)\n" |
112 | " comp_extension (COUNTER_COMPONENT_EXTENSION)\n" |
113 | "\n" |
114 | " evt_ovf (COUNTER_EVENT_OVERFLOW) [default: evt_ovf]\n" |
115 | " evt_udf (COUNTER_EVENT_UNDERFLOW)\n" |
116 | " evt_ovf_udf (COUNTER_EVENT_OVERFLOW_UNDERFLOW)\n" |
117 | " evt_threshold (COUNTER_EVENT_THRESHOLD)\n" |
118 | " evt_index (COUNTER_EVENT_INDEX)\n" |
119 | " evt_change_of_state (COUNTER_EVENT_CHANGE_OF_STATE)\n" |
120 | " evt_capture (COUNTER_EVENT_CAPTURE)\n" |
121 | "\n" |
122 | " chan=<n> channel <n> for this watch [default: 0]\n" |
123 | " id=<n> component id <n> for this watch [default: 0]\n" |
124 | " parent=<n> component parent <n> for this watch [default: 0]\n" |
125 | "\n" |
126 | "Example with two watched events:\n\n" |
127 | "counter_watch_events -d \\\n" |
128 | "\t-w comp_count,scope_count,evt_ovf_udf \\\n" |
129 | "\t-w comp_extension,scope_count,evt_capture,id=7,chan=3\n" |
130 | ); |
131 | } |
132 | |
133 | static const struct option longopts[] = { |
134 | { "debug" , no_argument, 0, 'd' }, |
135 | { "help" , no_argument, 0, 'h' }, |
136 | { "device-num" , required_argument, 0, 'n' }, |
137 | { "loop" , required_argument, 0, 'l' }, |
138 | { "watch" , required_argument, 0, 'w' }, |
139 | { }, |
140 | }; |
141 | |
142 | /* counter watch subopts */ |
143 | enum { |
144 | WATCH_SCOPE_DEVICE, |
145 | WATCH_SCOPE_SIGNAL, |
146 | WATCH_SCOPE_COUNT, |
147 | WATCH_COMPONENT_NONE, |
148 | WATCH_COMPONENT_SIGNAL, |
149 | WATCH_COMPONENT_COUNT, |
150 | WATCH_COMPONENT_FUNCTION, |
151 | WATCH_COMPONENT_SYNAPSE_ACTION, |
152 | WATCH_COMPONENT_EXTENSION, |
153 | WATCH_EVENT_OVERFLOW, |
154 | WATCH_EVENT_UNDERFLOW, |
155 | WATCH_EVENT_OVERFLOW_UNDERFLOW, |
156 | WATCH_EVENT_THRESHOLD, |
157 | WATCH_EVENT_INDEX, |
158 | WATCH_EVENT_CHANGE_OF_STATE, |
159 | WATCH_EVENT_CAPTURE, |
160 | WATCH_CHANNEL, |
161 | WATCH_ID, |
162 | WATCH_PARENT, |
163 | WATCH_SUBOPTS_MAX, |
164 | }; |
165 | |
166 | static char * const counter_watch_subopts[WATCH_SUBOPTS_MAX + 1] = { |
167 | /* component.scope */ |
168 | [WATCH_SCOPE_DEVICE] = "scope_device" , |
169 | [WATCH_SCOPE_SIGNAL] = "scope_signal" , |
170 | [WATCH_SCOPE_COUNT] = "scope_count" , |
171 | /* component.type */ |
172 | [WATCH_COMPONENT_NONE] = "comp_none" , |
173 | [WATCH_COMPONENT_SIGNAL] = "comp_signal" , |
174 | [WATCH_COMPONENT_COUNT] = "comp_count" , |
175 | [WATCH_COMPONENT_FUNCTION] = "comp_function" , |
176 | [WATCH_COMPONENT_SYNAPSE_ACTION] = "comp_synapse_action" , |
177 | [WATCH_COMPONENT_EXTENSION] = "comp_extension" , |
178 | /* event */ |
179 | [WATCH_EVENT_OVERFLOW] = "evt_ovf" , |
180 | [WATCH_EVENT_UNDERFLOW] = "evt_udf" , |
181 | [WATCH_EVENT_OVERFLOW_UNDERFLOW] = "evt_ovf_udf" , |
182 | [WATCH_EVENT_THRESHOLD] = "evt_threshold" , |
183 | [WATCH_EVENT_INDEX] = "evt_index" , |
184 | [WATCH_EVENT_CHANGE_OF_STATE] = "evt_change_of_state" , |
185 | [WATCH_EVENT_CAPTURE] = "evt_capture" , |
186 | /* channel, id, parent */ |
187 | [WATCH_CHANNEL] = "chan" , |
188 | [WATCH_ID] = "id" , |
189 | [WATCH_PARENT] = "parent" , |
190 | /* Empty entry ends the opts array */ |
191 | NULL |
192 | }; |
193 | |
194 | int main(int argc, char **argv) |
195 | { |
196 | int c, fd, i, ret, rc = 0, debug = 0, loop = 0, dev_num = 0, nwatch = 0; |
197 | struct counter_event event_data; |
198 | char *device_name = NULL, *subopts, *value; |
199 | struct counter_watch *watches; |
200 | |
201 | /* |
202 | * 1st pass: |
203 | * - list watch events number to allocate the watch array. |
204 | * - parse normal options (other than watch options) |
205 | */ |
206 | while ((c = getopt_long(argc, argv, "dhn:l:w:" , longopts, NULL)) != -1) { |
207 | switch (c) { |
208 | case 'd': |
209 | debug = 1; |
210 | break; |
211 | case 'h': |
212 | print_usage(); |
213 | return EXIT_SUCCESS; |
214 | case 'n': |
215 | dev_num = strtoul(optarg, NULL, 10); |
216 | if (errno) { |
217 | perror("strtol failed: --device-num <n>\n" ); |
218 | return EXIT_FAILURE; |
219 | } |
220 | break; |
221 | case 'l': |
222 | loop = strtol(optarg, NULL, 10); |
223 | if (errno) { |
224 | perror("strtol failed: --loop <n>\n" ); |
225 | return EXIT_FAILURE; |
226 | } |
227 | break; |
228 | case 'w': |
229 | nwatch++; |
230 | break; |
231 | default: |
232 | return EXIT_FAILURE; |
233 | } |
234 | } |
235 | |
236 | if (nwatch) { |
237 | watches = calloc(nwatch, sizeof(*watches)); |
238 | if (!watches) { |
239 | perror("Error allocating watches\n" ); |
240 | return EXIT_FAILURE; |
241 | } |
242 | } else { |
243 | /* default to simple watch example */ |
244 | watches = simple_watch; |
245 | nwatch = ARRAY_SIZE(simple_watch); |
246 | } |
247 | |
248 | /* 2nd pass: parse watch sub-options to fill in watch array */ |
249 | optind = 1; |
250 | i = 0; |
251 | while ((c = getopt_long(argc, argv, "dhn:l:w:" , longopts, NULL)) != -1) { |
252 | switch (c) { |
253 | case 'w': |
254 | subopts = optarg; |
255 | while (*subopts != '\0') { |
256 | ret = getsubopt(&subopts, counter_watch_subopts, &value); |
257 | switch (ret) { |
258 | case WATCH_SCOPE_DEVICE: |
259 | case WATCH_SCOPE_SIGNAL: |
260 | case WATCH_SCOPE_COUNT: |
261 | /* match with counter_scope */ |
262 | watches[i].component.scope = ret; |
263 | break; |
264 | case WATCH_COMPONENT_NONE: |
265 | case WATCH_COMPONENT_SIGNAL: |
266 | case WATCH_COMPONENT_COUNT: |
267 | case WATCH_COMPONENT_FUNCTION: |
268 | case WATCH_COMPONENT_SYNAPSE_ACTION: |
269 | case WATCH_COMPONENT_EXTENSION: |
270 | /* match counter_component_type: subtract enum value */ |
271 | ret -= WATCH_COMPONENT_NONE; |
272 | watches[i].component.type = ret; |
273 | break; |
274 | case WATCH_EVENT_OVERFLOW: |
275 | case WATCH_EVENT_UNDERFLOW: |
276 | case WATCH_EVENT_OVERFLOW_UNDERFLOW: |
277 | case WATCH_EVENT_THRESHOLD: |
278 | case WATCH_EVENT_INDEX: |
279 | case WATCH_EVENT_CHANGE_OF_STATE: |
280 | case WATCH_EVENT_CAPTURE: |
281 | /* match counter_event_type: subtract enum value */ |
282 | ret -= WATCH_EVENT_OVERFLOW; |
283 | watches[i].event = ret; |
284 | break; |
285 | case WATCH_CHANNEL: |
286 | if (!value) { |
287 | fprintf(stderr, "Invalid chan=<number>\n" ); |
288 | rc = EXIT_FAILURE; |
289 | goto err_free_watches; |
290 | } |
291 | watches[i].channel = strtoul(value, NULL, 10); |
292 | if (errno) { |
293 | perror("strtoul failed: chan=<number>\n" ); |
294 | rc = EXIT_FAILURE; |
295 | goto err_free_watches; |
296 | } |
297 | break; |
298 | case WATCH_ID: |
299 | if (!value) { |
300 | fprintf(stderr, "Invalid id=<number>\n" ); |
301 | rc = EXIT_FAILURE; |
302 | goto err_free_watches; |
303 | } |
304 | watches[i].component.id = strtoul(value, NULL, 10); |
305 | if (errno) { |
306 | perror("strtoul failed: id=<number>\n" ); |
307 | rc = EXIT_FAILURE; |
308 | goto err_free_watches; |
309 | } |
310 | break; |
311 | case WATCH_PARENT: |
312 | if (!value) { |
313 | fprintf(stderr, "Invalid parent=<number>\n" ); |
314 | rc = EXIT_FAILURE; |
315 | goto err_free_watches; |
316 | } |
317 | watches[i].component.parent = strtoul(value, NULL, 10); |
318 | if (errno) { |
319 | perror("strtoul failed: parent=<number>\n" ); |
320 | rc = EXIT_FAILURE; |
321 | goto err_free_watches; |
322 | } |
323 | break; |
324 | default: |
325 | fprintf(stderr, "Unknown suboption '%s'\n" , value); |
326 | rc = EXIT_FAILURE; |
327 | goto err_free_watches; |
328 | } |
329 | } |
330 | i++; |
331 | break; |
332 | } |
333 | } |
334 | |
335 | if (debug) |
336 | print_watch(watch: watches, nwatch); |
337 | |
338 | ret = asprintf(&device_name, "/dev/counter%d" , dev_num); |
339 | if (ret < 0) { |
340 | fprintf(stderr, "asprintf failed\n" ); |
341 | rc = EXIT_FAILURE; |
342 | goto err_free_watches; |
343 | } |
344 | |
345 | if (debug) |
346 | printf("Opening %s\n" , device_name); |
347 | |
348 | fd = open(device_name, O_RDWR); |
349 | if (fd == -1) { |
350 | fprintf(stderr, "Unable to open %s: %s\n" , device_name, strerror(errno)); |
351 | free(device_name); |
352 | rc = EXIT_FAILURE; |
353 | goto err_free_watches; |
354 | } |
355 | free(device_name); |
356 | |
357 | for (i = 0; i < nwatch; i++) { |
358 | ret = ioctl(fd, COUNTER_ADD_WATCH_IOCTL, watches + i); |
359 | if (ret == -1) { |
360 | fprintf(stderr, "Error adding watches[%d]: %s\n" , i, |
361 | strerror(errno)); |
362 | rc = EXIT_FAILURE; |
363 | goto err_close; |
364 | } |
365 | } |
366 | |
367 | ret = ioctl(fd, COUNTER_ENABLE_EVENTS_IOCTL); |
368 | if (ret == -1) { |
369 | perror("Error enabling events" ); |
370 | rc = EXIT_FAILURE; |
371 | goto err_close; |
372 | } |
373 | |
374 | for (i = 0; loop <= 0 || i < loop; i++) { |
375 | ret = read(fd, &event_data, sizeof(event_data)); |
376 | if (ret == -1) { |
377 | perror("Failed to read event data" ); |
378 | rc = EXIT_FAILURE; |
379 | goto err_close; |
380 | } |
381 | |
382 | if (ret != sizeof(event_data)) { |
383 | fprintf(stderr, "Failed to read event data (got: %d)\n" , ret); |
384 | rc = EXIT_FAILURE; |
385 | goto err_close; |
386 | } |
387 | |
388 | printf("Timestamp: %llu\tData: %llu\t event: %s\tch: %d\n" , |
389 | event_data.timestamp, event_data.value, |
390 | counter_event_type_name[event_data.watch.event], |
391 | event_data.watch.channel); |
392 | |
393 | if (event_data.status) { |
394 | fprintf(stderr, "Error %d: %s\n" , event_data.status, |
395 | strerror(event_data.status)); |
396 | } |
397 | } |
398 | |
399 | err_close: |
400 | close(fd); |
401 | err_free_watches: |
402 | if (watches != simple_watch) |
403 | free(watches); |
404 | |
405 | return rc; |
406 | } |
407 | |