mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2024-12-29 17:23:36 +00:00
tools/counter: add a flexible watch events tool
This adds a new counter tool to be able to test various watch events. A flexible watch array can be populated from command line, each field may be tuned with a dedicated command line sub-option in "--watch" string. Several watch events can be defined, each can have specific watch options, by using "--watch <watch 1 options> --watch <watch 2 options>". Watch options is a comma separated list. It also comes with a simple default watch (to monitor overflow/underflow events), used when no watch parameters are provided. It's equivalent to: counter_watch_events -w comp_count,scope_count,evt_ovf_udf The print_usage() routine proposes another example, from the command line, which generates a 2 elements watch array, to monitor: - overflow underflow events - capture events, on channel 3, that reads read captured data by specifying the component id (capture3_component_id being 7 here). Signed-off-by: Fabrice Gasnier <fabrice.gasnier@foss.st.com> Link: https://lore.kernel.org/r/20231213173117.4174511-2-fabrice.gasnier@foss.st.com Signed-off-by: William Breathitt Gray <william.gray@linaro.org>
This commit is contained in:
parent
a39b6ac378
commit
1e73427f66
@ -1 +1,2 @@
|
||||
counter_example-y += counter_example.o
|
||||
counter_watch_events-y += counter_watch_events.o
|
||||
|
@ -12,9 +12,10 @@ endif
|
||||
# (this improves performance and avoids hard-to-debug behaviour);
|
||||
MAKEFLAGS += -r
|
||||
|
||||
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include
|
||||
override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include \
|
||||
-I$(srctree)/tools/include
|
||||
|
||||
ALL_TARGETS := counter_example
|
||||
ALL_TARGETS := counter_example counter_watch_events
|
||||
ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS))
|
||||
|
||||
all: $(ALL_PROGRAMS)
|
||||
@ -37,12 +38,19 @@ $(COUNTER_EXAMPLE): prepare FORCE
|
||||
$(OUTPUT)counter_example: $(COUNTER_EXAMPLE)
|
||||
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
|
||||
|
||||
COUNTER_WATCH_EVENTS := $(OUTPUT)counter_watch_events.o
|
||||
$(COUNTER_WATCH_EVENTS): prepare FORCE
|
||||
$(Q)$(MAKE) $(build)=counter_watch_events
|
||||
$(OUTPUT)counter_watch_events: $(COUNTER_WATCH_EVENTS)
|
||||
$(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(ALL_PROGRAMS)
|
||||
rm -rf $(OUTPUT)include/linux/counter.h
|
||||
rm -df $(OUTPUT)include/linux
|
||||
rm -df $(OUTPUT)include
|
||||
find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete
|
||||
find $(or $(OUTPUT),.) -name '\.*.o.cmd' -delete
|
||||
|
||||
install: $(ALL_PROGRAMS)
|
||||
install -d -m 755 $(DESTDIR)$(bindir); \
|
||||
|
406
tools/counter/counter_watch_events.c
Normal file
406
tools/counter/counter_watch_events.c
Normal file
@ -0,0 +1,406 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Counter Watch Events - Test various counter watch events in a userspace application
|
||||
*
|
||||
* Copyright (C) STMicroelectronics 2023 - All Rights Reserved
|
||||
* Author: Fabrice Gasnier <fabrice.gasnier@foss.st.com>.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/counter.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static struct counter_watch simple_watch[] = {
|
||||
{
|
||||
/* Component data: Count 0 count */
|
||||
.component.type = COUNTER_COMPONENT_COUNT,
|
||||
.component.scope = COUNTER_SCOPE_COUNT,
|
||||
.component.parent = 0,
|
||||
/* Event type: overflow or underflow */
|
||||
.event = COUNTER_EVENT_OVERFLOW_UNDERFLOW,
|
||||
/* Device event channel 0 */
|
||||
.channel = 0,
|
||||
},
|
||||
};
|
||||
|
||||
static const char * const counter_event_type_name[] = {
|
||||
"COUNTER_EVENT_OVERFLOW",
|
||||
"COUNTER_EVENT_UNDERFLOW",
|
||||
"COUNTER_EVENT_OVERFLOW_UNDERFLOW",
|
||||
"COUNTER_EVENT_THRESHOLD",
|
||||
"COUNTER_EVENT_INDEX",
|
||||
"COUNTER_EVENT_CHANGE_OF_STATE",
|
||||
"COUNTER_EVENT_CAPTURE",
|
||||
};
|
||||
|
||||
static const char * const counter_component_type_name[] = {
|
||||
"COUNTER_COMPONENT_NONE",
|
||||
"COUNTER_COMPONENT_SIGNAL",
|
||||
"COUNTER_COMPONENT_COUNT",
|
||||
"COUNTER_COMPONENT_FUNCTION",
|
||||
"COUNTER_COMPONENT_SYNAPSE_ACTION",
|
||||
"COUNTER_COMPONENT_EXTENSION",
|
||||
};
|
||||
|
||||
static const char * const counter_scope_name[] = {
|
||||
"COUNTER_SCOPE_DEVICE",
|
||||
"COUNTER_SCOPE_SIGNAL",
|
||||
"COUNTER_SCOPE_COUNT",
|
||||
};
|
||||
|
||||
static void print_watch(struct counter_watch *watch, int nwatch)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* prints the watch array in C-like structure */
|
||||
printf("watch[%d] = {\n", nwatch);
|
||||
for (i = 0; i < nwatch; i++) {
|
||||
printf(" [%d] =\t{\n"
|
||||
"\t\t.component.type = %s\n"
|
||||
"\t\t.component.scope = %s\n"
|
||||
"\t\t.component.parent = %d\n"
|
||||
"\t\t.component.id = %d\n"
|
||||
"\t\t.event = %s\n"
|
||||
"\t\t.channel = %d\n"
|
||||
"\t},\n",
|
||||
i,
|
||||
counter_component_type_name[watch[i].component.type],
|
||||
counter_scope_name[watch[i].component.scope],
|
||||
watch[i].component.parent,
|
||||
watch[i].component.id,
|
||||
counter_event_type_name[watch[i].event],
|
||||
watch[i].channel);
|
||||
}
|
||||
printf("};\n");
|
||||
}
|
||||
|
||||
static void print_usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage:\n\n"
|
||||
"counter_watch_events [options] [-w <watchoptions>]\n"
|
||||
"counter_watch_events [options] [-w <watch1 options>] [-w <watch2 options>]...\n"
|
||||
"\n"
|
||||
"When no --watch option has been provided, simple watch example is used:\n"
|
||||
"counter_watch_events [options] -w comp_count,scope_count,evt_ovf_udf\n"
|
||||
"\n"
|
||||
"Test various watch events for given counter device.\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -d, --debug Prints debug information\n"
|
||||
" -h, --help Prints usage\n"
|
||||
" -n, --device-num <n> Use /dev/counter<n> [default: /dev/counter0]\n"
|
||||
" -l, --loop <n> Loop for <n> events [default: 0 (forever)]\n"
|
||||
" -w, --watch <watchoptions> comma-separated list of watch options\n"
|
||||
"\n"
|
||||
"Watch options:\n"
|
||||
" scope_device (COUNTER_SCOPE_DEVICE) [default: scope_device]\n"
|
||||
" scope_signal (COUNTER_SCOPE_SIGNAL)\n"
|
||||
" scope_count (COUNTER_SCOPE_COUNT)\n"
|
||||
"\n"
|
||||
" comp_none (COUNTER_COMPONENT_NONE) [default: comp_none]\n"
|
||||
" comp_signal (COUNTER_COMPONENT_SIGNAL)\n"
|
||||
" comp_count (COUNTER_COMPONENT_COUNT)\n"
|
||||
" comp_function (COUNTER_COMPONENT_FUNCTION)\n"
|
||||
" comp_synapse_action (COUNTER_COMPONENT_SYNAPSE_ACTION)\n"
|
||||
" comp_extension (COUNTER_COMPONENT_EXTENSION)\n"
|
||||
"\n"
|
||||
" evt_ovf (COUNTER_EVENT_OVERFLOW) [default: evt_ovf]\n"
|
||||
" evt_udf (COUNTER_EVENT_UNDERFLOW)\n"
|
||||
" evt_ovf_udf (COUNTER_EVENT_OVERFLOW_UNDERFLOW)\n"
|
||||
" evt_threshold (COUNTER_EVENT_THRESHOLD)\n"
|
||||
" evt_index (COUNTER_EVENT_INDEX)\n"
|
||||
" evt_change_of_state (COUNTER_EVENT_CHANGE_OF_STATE)\n"
|
||||
" evt_capture (COUNTER_EVENT_CAPTURE)\n"
|
||||
"\n"
|
||||
" chan=<n> channel <n> for this watch [default: 0]\n"
|
||||
" id=<n> componend id <n> for this watch [default: 0]\n"
|
||||
" parent=<n> componend parent <n> for this watch [default: 0]\n"
|
||||
"\n"
|
||||
"Example with two watched events:\n\n"
|
||||
"counter_watch_events -d \\\n"
|
||||
"\t-w comp_count,scope_count,evt_ovf_udf \\\n"
|
||||
"\t-w comp_extension,scope_count,evt_capture,id=7,chan=3\n"
|
||||
);
|
||||
}
|
||||
|
||||
static const struct option longopts[] = {
|
||||
{ "debug", no_argument, 0, 'd' },
|
||||
{ "help", no_argument, 0, 'h' },
|
||||
{ "device-num", required_argument, 0, 'n' },
|
||||
{ "loop", required_argument, 0, 'l' },
|
||||
{ "watch", required_argument, 0, 'w' },
|
||||
{ },
|
||||
};
|
||||
|
||||
/* counter watch subopts */
|
||||
enum {
|
||||
WATCH_SCOPE_DEVICE,
|
||||
WATCH_SCOPE_SIGNAL,
|
||||
WATCH_SCOPE_COUNT,
|
||||
WATCH_COMPONENT_NONE,
|
||||
WATCH_COMPONENT_SIGNAL,
|
||||
WATCH_COMPONENT_COUNT,
|
||||
WATCH_COMPONENT_FUNCTION,
|
||||
WATCH_COMPONENT_SYNAPSE_ACTION,
|
||||
WATCH_COMPONENT_EXTENSION,
|
||||
WATCH_EVENT_OVERFLOW,
|
||||
WATCH_EVENT_UNDERFLOW,
|
||||
WATCH_EVENT_OVERFLOW_UNDERFLOW,
|
||||
WATCH_EVENT_THRESHOLD,
|
||||
WATCH_EVENT_INDEX,
|
||||
WATCH_EVENT_CHANGE_OF_STATE,
|
||||
WATCH_EVENT_CAPTURE,
|
||||
WATCH_CHANNEL,
|
||||
WATCH_ID,
|
||||
WATCH_PARENT,
|
||||
WATCH_SUBOPTS_MAX,
|
||||
};
|
||||
|
||||
static char * const counter_watch_subopts[WATCH_SUBOPTS_MAX + 1] = {
|
||||
/* component.scope */
|
||||
[WATCH_SCOPE_DEVICE] = "scope_device",
|
||||
[WATCH_SCOPE_SIGNAL] = "scope_signal",
|
||||
[WATCH_SCOPE_COUNT] = "scope_count",
|
||||
/* component.type */
|
||||
[WATCH_COMPONENT_NONE] = "comp_none",
|
||||
[WATCH_COMPONENT_SIGNAL] = "comp_signal",
|
||||
[WATCH_COMPONENT_COUNT] = "comp_count",
|
||||
[WATCH_COMPONENT_FUNCTION] = "comp_function",
|
||||
[WATCH_COMPONENT_SYNAPSE_ACTION] = "comp_synapse_action",
|
||||
[WATCH_COMPONENT_EXTENSION] = "comp_extension",
|
||||
/* event */
|
||||
[WATCH_EVENT_OVERFLOW] = "evt_ovf",
|
||||
[WATCH_EVENT_UNDERFLOW] = "evt_udf",
|
||||
[WATCH_EVENT_OVERFLOW_UNDERFLOW] = "evt_ovf_udf",
|
||||
[WATCH_EVENT_THRESHOLD] = "evt_threshold",
|
||||
[WATCH_EVENT_INDEX] = "evt_index",
|
||||
[WATCH_EVENT_CHANGE_OF_STATE] = "evt_change_of_state",
|
||||
[WATCH_EVENT_CAPTURE] = "evt_capture",
|
||||
/* channel, id, parent */
|
||||
[WATCH_CHANNEL] = "chan",
|
||||
[WATCH_ID] = "id",
|
||||
[WATCH_PARENT] = "parent",
|
||||
/* Empty entry ends the opts array */
|
||||
NULL
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int c, fd, i, ret, rc = 0, debug = 0, loop = 0, dev_num = 0, nwatch = 0;
|
||||
struct counter_event event_data;
|
||||
char *device_name = NULL, *subopts, *value;
|
||||
struct counter_watch *watches;
|
||||
|
||||
/*
|
||||
* 1st pass:
|
||||
* - list watch events number to allocate the watch array.
|
||||
* - parse normal options (other than watch options)
|
||||
*/
|
||||
while ((c = getopt_long(argc, argv, "dhn:l:w:", longopts, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'd':
|
||||
debug = 1;
|
||||
break;
|
||||
case 'h':
|
||||
print_usage();
|
||||
return EXIT_SUCCESS;
|
||||
case 'n':
|
||||
dev_num = strtoul(optarg, NULL, 10);
|
||||
if (errno) {
|
||||
perror("strtol failed: --device-num <n>\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
loop = strtol(optarg, NULL, 10);
|
||||
if (errno) {
|
||||
perror("strtol failed: --loop <n>\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
case 'w':
|
||||
nwatch++;
|
||||
break;
|
||||
default:
|
||||
return EXIT_FAILURE;
|
||||
};
|
||||
};
|
||||
|
||||
if (nwatch) {
|
||||
watches = calloc(nwatch, sizeof(*watches));
|
||||
if (!watches) {
|
||||
perror("Error allocating watches\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
} else {
|
||||
/* default to simple watch example */
|
||||
watches = simple_watch;
|
||||
nwatch = ARRAY_SIZE(simple_watch);
|
||||
}
|
||||
|
||||
/* 2nd pass: parse watch sub-options to fill in watch array */
|
||||
optind = 1;
|
||||
i = 0;
|
||||
while ((c = getopt_long(argc, argv, "dhn:l:w:", longopts, NULL)) != -1) {
|
||||
switch (c) {
|
||||
case 'w':
|
||||
subopts = optarg;
|
||||
while (*subopts != '\0') {
|
||||
ret = getsubopt(&subopts, counter_watch_subopts, &value);
|
||||
switch (ret) {
|
||||
case WATCH_SCOPE_DEVICE:
|
||||
case WATCH_SCOPE_SIGNAL:
|
||||
case WATCH_SCOPE_COUNT:
|
||||
/* match with counter_scope */
|
||||
watches[i].component.scope = ret;
|
||||
break;
|
||||
case WATCH_COMPONENT_NONE:
|
||||
case WATCH_COMPONENT_SIGNAL:
|
||||
case WATCH_COMPONENT_COUNT:
|
||||
case WATCH_COMPONENT_FUNCTION:
|
||||
case WATCH_COMPONENT_SYNAPSE_ACTION:
|
||||
case WATCH_COMPONENT_EXTENSION:
|
||||
/* match counter_component_type: subtract enum value */
|
||||
ret -= WATCH_COMPONENT_NONE;
|
||||
watches[i].component.type = ret;
|
||||
break;
|
||||
case WATCH_EVENT_OVERFLOW:
|
||||
case WATCH_EVENT_UNDERFLOW:
|
||||
case WATCH_EVENT_OVERFLOW_UNDERFLOW:
|
||||
case WATCH_EVENT_THRESHOLD:
|
||||
case WATCH_EVENT_INDEX:
|
||||
case WATCH_EVENT_CHANGE_OF_STATE:
|
||||
case WATCH_EVENT_CAPTURE:
|
||||
/* match counter_event_type: subtract enum value */
|
||||
ret -= WATCH_EVENT_OVERFLOW;
|
||||
watches[i].event = ret;
|
||||
break;
|
||||
case WATCH_CHANNEL:
|
||||
if (!value) {
|
||||
fprintf(stderr, "Invalid chan=<number>\n");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
watches[i].channel = strtoul(value, NULL, 10);
|
||||
if (errno) {
|
||||
perror("strtoul failed: chan=<number>\n");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
break;
|
||||
case WATCH_ID:
|
||||
if (!value) {
|
||||
fprintf(stderr, "Invalid id=<number>\n");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
watches[i].component.id = strtoul(value, NULL, 10);
|
||||
if (errno) {
|
||||
perror("strtoul failed: id=<number>\n");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
break;
|
||||
case WATCH_PARENT:
|
||||
if (!value) {
|
||||
fprintf(stderr, "Invalid parent=<number>\n");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
watches[i].component.parent = strtoul(value, NULL, 10);
|
||||
if (errno) {
|
||||
perror("strtoul failed: parent=<number>\n");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown suboption '%s'\n", value);
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (debug)
|
||||
print_watch(watches, nwatch);
|
||||
|
||||
ret = asprintf(&device_name, "/dev/counter%d", dev_num);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "asprintf failed\n");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
|
||||
if (debug)
|
||||
printf("Opening %s\n", device_name);
|
||||
|
||||
fd = open(device_name, O_RDWR);
|
||||
if (fd == -1) {
|
||||
fprintf(stderr, "Unable to open %s: %s\n", device_name, strerror(errno));
|
||||
free(device_name);
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_free_watches;
|
||||
}
|
||||
free(device_name);
|
||||
|
||||
for (i = 0; i < nwatch; i++) {
|
||||
ret = ioctl(fd, COUNTER_ADD_WATCH_IOCTL, watches + i);
|
||||
if (ret == -1) {
|
||||
fprintf(stderr, "Error adding watches[%d]: %s\n", i,
|
||||
strerror(errno));
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_close;
|
||||
}
|
||||
}
|
||||
|
||||
ret = ioctl(fd, COUNTER_ENABLE_EVENTS_IOCTL);
|
||||
if (ret == -1) {
|
||||
perror("Error enabling events");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_close;
|
||||
}
|
||||
|
||||
for (i = 0; loop <= 0 || i < loop; i++) {
|
||||
ret = read(fd, &event_data, sizeof(event_data));
|
||||
if (ret == -1) {
|
||||
perror("Failed to read event data");
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_close;
|
||||
}
|
||||
|
||||
if (ret != sizeof(event_data)) {
|
||||
fprintf(stderr, "Failed to read event data (got: %d)\n", ret);
|
||||
rc = EXIT_FAILURE;
|
||||
goto err_close;
|
||||
}
|
||||
|
||||
printf("Timestamp: %llu\tData: %llu\t event: %s\tch: %d\n",
|
||||
event_data.timestamp, event_data.value,
|
||||
counter_event_type_name[event_data.watch.event],
|
||||
event_data.watch.channel);
|
||||
|
||||
if (event_data.status) {
|
||||
fprintf(stderr, "Error %d: %s\n", event_data.status,
|
||||
strerror(event_data.status));
|
||||
}
|
||||
}
|
||||
|
||||
err_close:
|
||||
close(fd);
|
||||
err_free_watches:
|
||||
if (watches != simple_watch)
|
||||
free(watches);
|
||||
|
||||
return rc;
|
||||
}
|
Loading…
Reference in New Issue
Block a user