root/usr.sbin/intrctl/intrctl.c
/*      $NetBSD: intrctl.c,v 1.12 2021/02/22 11:33:34 jmcneill Exp $    */

/*
 * Copyright (c) 2015 Internet Initiative Japan Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: intrctl.c,v 1.12 2021/02/22 11:33:34 jmcneill Exp $");

#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/intrio.h>
#include <sys/types.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "intrctl_io.h"

__dead static void      usage(void);

int             verbose;

static void     intrctl_list(int, char **);
static void     intrctl_affinity(int, char **);
static void     intrctl_intr(int, char **);
static void     intrctl_nointr(int, char **);

static struct cmdtab {
        const char      *label;
        void    (*func)(int, char **);
} const intrctl_cmdtab[] = {
        { "list", intrctl_list },
        { "affinity", intrctl_affinity },
        { "intr", intrctl_intr },
        { "nointr", intrctl_nointr },
        { NULL, NULL },
};

int
main(int argc, char **argv)
{
        const struct cmdtab *ct;
        char *cmdname;

        if (argc < 2)
                usage();

        cmdname = argv[1];
        argv += 1;
        argc -= 1;

        for (ct = intrctl_cmdtab; ct->label != NULL; ct++) {
                if (strcmp(cmdname, ct->label) == 0) {
                        break;
                }
        }
        if (ct->label == NULL)
                errx(EXIT_FAILURE, "unknown command ``%s''", cmdname);

        (*ct->func)(argc, argv);
        exit(EXIT_SUCCESS);
        /* NOTREACHED */
}

static void
usage(void)
{
        const char *progname = getprogname();

        fprintf(stderr, "usage: %s list [-cz] [-w secs]\n", progname);
        fprintf(stderr, "       %s affinity -i interrupt_name -c cpu_index\n",
            progname);
        fprintf(stderr, "       %s intr -c cpu_index\n", progname);
        fprintf(stderr, "       %s nointr -c cpu_index\n", progname);
        exit(EXIT_FAILURE);
        /* NOTREACHED */
}

static int intrctl_io_alloc_retry_count = 4;

static bool
intrctl_list_line_allcpus(struct intrio_list_line *illine, int ncpus)
{
        struct intrio_list_line_cpu *illc;
        int i;

        for (i = 0; i < ncpus; i++) {
                illc = &illine->ill_cpu[i];
                if (illc->illc_assigned == false) {
                        return false;
                }
        }

        return true;
}

static void
intrctl_list_one(bool compact, bool skipzero)
{
        char buf[64];
        struct intrio_list_line *illine;
        int i, ncpus, *cpucol;
        void *handle;
        size_t intridlen;

        handle = intrctl_io_alloc(intrctl_io_alloc_retry_count);
        if (handle == NULL)
                err(EXIT_FAILURE, "intrctl_io_alloc");

        /* calc columns */
        ncpus = intrctl_io_ncpus(handle);
        intridlen = strlen("interrupt id");
        for (illine = intrctl_io_firstline(handle); illine != NULL;
            illine = intrctl_io_nextline(handle, illine)) {
                size_t len = strlen(illine->ill_intrid);
                if (intridlen < len)
                        intridlen = len;
        }

        cpucol = malloc(sizeof(*cpucol) * (size_t)ncpus);
        if (cpucol == NULL)
                err(EXIT_FAILURE, "malloc");
        for (i = 0; i < ncpus; i++) {
                snprintf(buf, sizeof(buf), "CPU%u", i);
                cpucol[i] = strlen(buf);
        }
        for (illine = intrctl_io_firstline(handle); illine != NULL;
            illine = intrctl_io_nextline(handle, illine)) {
                for (i = 0; i < ncpus; i++) {
                        int len;
                        snprintf(buf, sizeof(buf), "%" PRIu64,
                            illine->ill_cpu[i].illc_count);
                        len = (int)strlen(buf);
                        if (cpucol[i] < len)
                                cpucol[i] = len;
                }
        }

        /* header */
        printf("%-*s ", (int)intridlen, "interrupt id");
        if (compact) {
                printf("%20s ", "total");
                printf("%5s ", "aff");
        } else {
                for (i = 0; i < ncpus; i++) {
                        snprintf(buf, sizeof(buf), "CPU%u", i);
                        printf("%*s  ", cpucol[i], buf);
                }
        }
        printf("device name(s)\n");

        /* body */
        for (illine = intrctl_io_firstline(handle); illine != NULL;
            illine = intrctl_io_nextline(handle, illine)) {
                struct intrio_list_line_cpu *illc;

                if (skipzero) {
                        bool is_zero = true;

                        for (i = 0; i < ncpus; i++) {
                                illc = &illine->ill_cpu[i];
                                if (illc->illc_count != 0) {
                                        is_zero = false;
                                        break;
                                }
                        }
                        if (is_zero)
                                continue;
                }

                printf("%-*s ", (int)intridlen, illine->ill_intrid);
                if (compact) {
                        uint64_t total = 0;
                        bool allcpus = ncpus > 1 &&
                            intrctl_list_line_allcpus(illine, ncpus);
                        char *affinity = NULL, *oaffinity = NULL;
                        for (i = 0; i < ncpus; i++) {
                                illc = &illine->ill_cpu[i];
                                total += illc->illc_count;
                                if (allcpus && i != 0 && i != ncpus - 1) {
                                        continue;
                                }
                                if (illc->illc_assigned) {
                                        const char *sep = allcpus ? "-" : ", ";
                                        asprintf(&affinity, "%s%s%d",
                                            oaffinity ? oaffinity : "",
                                            oaffinity ? sep : "",
                                            i);
                                        if (oaffinity)
                                                free(oaffinity);
                                        oaffinity = affinity;
                                }
                        }
                        printf("%20" PRIu64 " ", total);
                        printf("%5s ", affinity ? affinity : "none");
                        if (affinity)
                                free(affinity);
                } else {
                        for (i = 0; i < ncpus; i++) {
                                illc = &illine->ill_cpu[i];
                                printf("%*" PRIu64 "%c ", cpucol[i], illc->illc_count,
                                    illc->illc_assigned ? '*' : ' ');
                        }
                }
                printf("%s\n", illine->ill_xname);
        }

        free(cpucol);
        intrctl_io_free(handle);
}

static void
intrctl_list(int argc, char **argv)
{
        int seconds = 0;
        bool compact = false;
        bool skipzero = false;
        int ch;

        while ((ch = getopt(argc, argv, "cw:z")) != -1) {
                switch (ch) {
                case 'c':
                        compact = true;
                        break;
                case 'z':
                        skipzero = true;
                        break;
                case 'w':
                        seconds = atoi(optarg);
                        if (seconds < 0)
                                errx(1, "seconds must be positive.");
                        break;
                default:
                        usage();
                }
        }

        for (;;) {
                intrctl_list_one(compact, skipzero);
                if (seconds == 0)
                        break;
                sleep(seconds);
        }
}

static void
intrctl_affinity(int argc, char **argv)
{
        struct intrio_set iset;
        cpuset_t *cpuset;
        unsigned long index;
        int ch, error;

        index = ULONG_MAX;
        memset(&iset.intrid, 0, sizeof(iset.intrid));

        while ((ch = getopt(argc, argv, "c:i:")) != -1) {
                switch (ch) {
                case 'c':
                        index = strtoul(optarg, NULL, 10);
                        break;
                case 'i':
                        if (strnlen(optarg, ARG_MAX) > INTRIDBUF)
                                usage();
                        strlcpy(iset.intrid, optarg, INTRIDBUF);
                        break;
                default:
                        usage();
                }
        }

        if (iset.intrid[0] == '\0' || index == ULONG_MAX)
                usage();

        if (index >= (u_long)sysconf(_SC_NPROCESSORS_CONF))
                err(EXIT_FAILURE, "invalid cpu index");

        cpuset = cpuset_create();
        if (cpuset == NULL)
                err(EXIT_FAILURE, "create_cpuset()");

        cpuset_zero(cpuset);
        cpuset_set(index, cpuset);
        iset.cpuset = cpuset;
        iset.cpuset_size = cpuset_size(cpuset);
        error = sysctlbyname("kern.intr.affinity", NULL, NULL, &iset, sizeof(iset));
        cpuset_destroy(cpuset);
        if (error < 0)
                err(EXIT_FAILURE, "sysctl kern.intr.affinity");
}

static void
intrctl_intr(int argc, char **argv)
{
        struct intrio_set iset;
        cpuset_t *cpuset;
        unsigned long index;
        int ch, error;

        index = ULONG_MAX;

        while ((ch = getopt(argc, argv, "c:")) != -1) {
                switch (ch) {
                case 'c':
                        index = strtoul(optarg, NULL, 10);
                        break;
                default:
                        usage();
                }
        }

        if (index == ULONG_MAX)
                usage();

        if (index >= (u_long)sysconf(_SC_NPROCESSORS_CONF))
                err(EXIT_FAILURE, "invalid cpu index");

        cpuset = cpuset_create();
        if (cpuset == NULL)
                err(EXIT_FAILURE, "create_cpuset()");

        cpuset_zero(cpuset);
        cpuset_set(index, cpuset);
        iset.cpuset = cpuset;
        iset.cpuset_size = cpuset_size(cpuset);
        error = sysctlbyname("kern.intr.intr", NULL, NULL, &iset, sizeof(iset));
        cpuset_destroy(cpuset);
        if (error < 0)
                err(EXIT_FAILURE, "sysctl kern.intr.intr");
}

static void
intrctl_nointr(int argc, char **argv)
{
        struct intrio_set iset;
        cpuset_t *cpuset;
        unsigned long index;
        int ch, error;

        index = ULONG_MAX;

        while ((ch = getopt(argc, argv, "c:")) != -1) {
                switch (ch) {
                case 'c':
                        index = strtoul(optarg, NULL, 10);
                        break;
                default:
                        usage();
                }
        }

        if (index == ULONG_MAX)
                usage();

        if (index >= (u_long)sysconf(_SC_NPROCESSORS_CONF))
                err(EXIT_FAILURE, "invalid cpu index");

        cpuset = cpuset_create();
        if (cpuset == NULL)
                err(EXIT_FAILURE, "create_cpuset()");

        cpuset_zero(cpuset);
        cpuset_set(index, cpuset);
        iset.cpuset = cpuset;
        iset.cpuset_size = cpuset_size(cpuset);
        error = sysctlbyname("kern.intr.nointr", NULL, NULL, &iset, sizeof(iset));
        cpuset_destroy(cpuset);
        if (error < 0)
                err(EXIT_FAILURE, "sysctl kern.intr.nointr");
}