root/tools/counter/counter_watch_events.c
// 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",
        "COUNTER_EVENT_DIRECTION_CHANGE",
};

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"
                "  evt_direction_change       (COUNTER_EVENT_DIRECTION_CHANGE)\n"
                "\n"
                "  chan=<n>                   channel <n> for this watch [default: 0]\n"
                "  id=<n>                     component id <n> for this watch [default: 0]\n"
                "  parent=<n>                 component 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_EVENT_DIRECTION_CHANGE,
        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",
        [WATCH_EVENT_DIRECTION_CHANGE] = "evt_direction_change",
        /* 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:
                                case WATCH_EVENT_DIRECTION_CHANGE:
                                        /* 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;
}