/*      $NetBSD: cpuctl.c,v 1.35 2023/09/13 06:53:23 wiz Exp $  */

/*-
* Copyright (c) 2007, 2008, 2009, 2012, 2015 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran.
*
* 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.
*/

#ifndef lint
#include <sys/cdefs.h>
__RCSID("$NetBSD: cpuctl.c,v 1.35 2023/09/13 06:53:23 wiz Exp $");
#endif /* not lint */

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <sys/cpuio.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include <time.h>
#include <sched.h>
#include <stdbool.h>

#include "cpuctl.h"

static u_int    getcpuid(char *);
__dead static void      usage(void);

static void     cpu_identify(char **);
static void     cpu_list(char **);
static void     cpu_offline(char **);
static void     cpu_online(char **);
static void     cpu_intr(char **);
static void     cpu_nointr(char **);
static void     cpu_ucode(char **);

static struct cmdtab {
       const char      *label;
       bool    takesargs;
       bool    argsoptional;
       void    (*func)(char **);
} const cpu_cmdtab[] = {
       { "identify",   true,  false, cpu_identify },
       { "list",       false, false, cpu_list },
       { "offline",    true,  false, cpu_offline },
       { "online",     true,  false, cpu_online },
       { "intr",       true,  false, cpu_intr },
       { "nointr",     true,  false, cpu_nointr },
       { "ucode",      true,  true,  cpu_ucode },
       { NULL,         false, false, NULL },
};

static int      fd;
int             verbose;

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

       while ((ch = getopt(argc, argv, "v")) != -1)
               switch (ch) {
               case 'v':
                       verbose = 1;
                       break;
               default:
                       usage();
               }
       argc -= optind;
       argv += optind;
       if (argc < 1)
               usage();

       if ((fd = open(_PATH_CPUCTL, O_RDWR)) < 0)
               err(EXIT_FAILURE, _PATH_CPUCTL);

       for (ct = cpu_cmdtab; ct->label != NULL; ct++) {
               if (strcmp(argv[0], ct->label) == 0) {
                       if (!ct->argsoptional &&
                           ((ct->takesargs == 0) ^ (argv[1] == NULL)))
                       {
                               usage();
                       }
                       (*ct->func)(argv + 1);
                       break;
               }
       }

       if (ct->label == NULL)
               errx(EXIT_FAILURE, "unknown command ``%s''", argv[0]);

       close(fd);
       exit(EXIT_SUCCESS);
       /* NOTREACHED */
}

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

       fprintf(stderr, "usage: %s identify cpuno [cpuno ..]\n", progname);
       fprintf(stderr, "       %s list\n", progname);
       fprintf(stderr, "       %s offline cpuno [cpuno ..]\n", progname);
       fprintf(stderr, "       %s online cpuno [cpuno ..]\n", progname);
       fprintf(stderr, "       %s intr cpuno [cpuno ..]\n", progname);
       fprintf(stderr, "       %s nointr cpuno [cpuno ..]\n", progname);
       fprintf(stderr, "       %s ucode [cpuno] [file]\n", progname);
       exit(EXIT_FAILURE);
       /* NOTREACHED */
}

static void
cpu_online(char **argv)
{
       cpustate_t cs;

       for (; *argv; argv++) {
               cs.cs_id = getcpuid(*argv);
               if (ioctl(fd, IOC_CPU_GETSTATE, &cs) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_GETSTATE");
               cs.cs_online = true;
               if (ioctl(fd, IOC_CPU_SETSTATE, &cs) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_SETSTATE");
       }
}

static void
cpu_offline(char **argv)
{
       cpustate_t cs;

       for (; *argv; argv++) {
               cs.cs_id = getcpuid(*argv);
               if (ioctl(fd, IOC_CPU_GETSTATE, &cs) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_GETSTATE");
               cs.cs_online = false;
               if (ioctl(fd, IOC_CPU_SETSTATE, &cs) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_SETSTATE");
       }
}

static void
cpu_intr(char **argv)
{
       cpustate_t cs;

       for (; *argv; argv++) {
               cs.cs_id = getcpuid(*argv);
               if (ioctl(fd, IOC_CPU_GETSTATE, &cs) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_GETSTATE");
               cs.cs_intr = true;
               if (ioctl(fd, IOC_CPU_SETSTATE, &cs) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_SETSTATE");
       }
}

static void
cpu_nointr(char **argv)
{
       cpustate_t cs;

       for (; *argv; argv++) {
               cs.cs_id = getcpuid(*argv);
               if (ioctl(fd, IOC_CPU_GETSTATE, &cs) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_GETSTATE");
               cs.cs_intr = false;
               if (ioctl(fd, IOC_CPU_SETSTATE, &cs) < 0) {
                       if (errno == EOPNOTSUPP) {
                               warnx("interrupt control not supported on "
                                   "this platform");
                       } else
                               err(EXIT_FAILURE, "IOC_CPU_SETSTATE");
               }
       }
}

static void
cpu_ucode(char **argv)
{
       int error;
       struct cpu_ucode uc;
       unsigned long id = 0; /* gcc */
       char *ep;
       cpuset_t *cpuset;

       uc.cpu_nr = -1;
       if (argv[0] != NULL) {
               id = strtoul(argv[0], &ep, 0);
               if (id != ULONG_MAX && *ep == '\0') {
                       uc.cpu_nr = id;
                       argv++;
               }
       }
       if (argv[0] != NULL)
               strlcpy(uc.fwname, argv[0], sizeof(uc.fwname));
       else
               memset(uc.fwname, '\0', sizeof(uc.fwname));

       error = ucodeupdate_check(fd, &uc);
       if (error)
               errx(EXIT_FAILURE, "unsupported");

       if (uc.cpu_nr == CPU_UCODE_CURRENT_CPU) {
               cpuset = cpuset_create();
               if (cpuset == NULL)
                       err(EXIT_FAILURE, "cpuset_create");
               cpuset_zero(cpuset);
               if (cpuset_set(id, cpuset) < 0)
                       err(EXIT_FAILURE, "cpuset_set");
               if (_sched_setaffinity(0, 0, cpuset_size(cpuset), cpuset) < 0) {
                       err(EXIT_FAILURE, "_sched_setaffinity");
               }
               cpuset_destroy(cpuset);
       }
       error = ioctl(fd, IOC_CPU_UCODE_APPLY, &uc);
       if (error < 0 && (verbose || errno != EEXIST)) {
               warnx("please also check dmesg(8) output for additional error information");
               if (uc.fwname[0])
                       err(EXIT_FAILURE, "%s", uc.fwname);
               else
                       err(EXIT_FAILURE, "IOC_CPU_UCODE_APPLY");
       }
}

static void
cpu_identify(char **argv)
{
       char name[32];
       unsigned int id, np;
       cpuset_t *cpuset;

       np = sysconf(_SC_NPROCESSORS_CONF);
       for (; *argv; argv++) {
               id = getcpuid(*argv);
               snprintf(name, sizeof(name), "cpu%u", id);

               if (identifycpu_bind() && np != 1) {
                       cpuset = cpuset_create();
                       if (cpuset == NULL)
                               err(EXIT_FAILURE, "cpuset_create");
                       cpuset_zero(cpuset);
                       if (cpuset_set(id, cpuset) < 0)
                               err(EXIT_FAILURE, "cpuset_set");
                       if (_sched_setaffinity(0, 0, cpuset_size(cpuset), cpuset) < 0) {
                               if (errno == EPERM) {
                                       printf("Cannot bind to target CPU.  Output "
                                           "may not accurately describe the target.\n"
                                           "Run as root to allow binding.\n\n");
                               } else {
                                       err(EXIT_FAILURE, "_sched_setaffinity");
                               }
                       }
                       cpuset_destroy(cpuset);
               }
               identifycpu(fd, name);
       }
}

static u_int
getcpuid(char *arg)
{
       char *argp;
       u_int id;
       long np;

       if (strncmp(arg, "cpu", 3) == 0)
               arg += 3;

       id = (u_int)strtoul(arg, &argp, 0);
       if (*argp != '\0')
               usage();

       np = sysconf(_SC_NPROCESSORS_CONF);
       if (id >= (u_long)np)
               errx(EXIT_FAILURE, "Invalid CPU number");

       return id;
}

static void
cpu_list(char **argv)
{
       const char *state, *intr;
       cpustate_t cs;
       u_int cnt, i;
       time_t lastmod;
       char ibuf[16], *ts;

       if (ioctl(fd, IOC_CPU_GETCOUNT, &cnt) < 0)
               err(EXIT_FAILURE, "IOC_CPU_GETCOUNT");

       printf(
"Num  HwId Unbound LWPs Interrupts Last change              #Intr\n"
"---- ---- ------------ ---------- ------------------------ -----\n");

       for (i = 0; i < cnt; i++) {
               cs.cs_id = i;
               if (ioctl(fd, IOC_CPU_GETSTATE, &cs) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_GETSTATE");
               if (ioctl(fd, IOC_CPU_MAPID, &cs.cs_id) < 0)
                       err(EXIT_FAILURE, "IOC_CPU_MAPID");
               if (cs.cs_online)
                       state = "online";
               else
                       state = "offline";
               if (cs.cs_intr)
                       intr = "intr";
               else
                       intr = "nointr";
               if (cs.cs_intrcnt == 0)
                       strcpy(ibuf, "?");
               else
                       snprintf(ibuf, sizeof(ibuf), "%d", cs.cs_intrcnt - 1);

               lastmod = (time_t)cs.cs_lastmod |
                   ((time_t)cs.cs_lastmodhi << 32);
               ts = asctime(localtime(&lastmod));
               ts[strlen(ts) - 1] = '\0';
               printf("%-4d %-4x %-12s %-10s %s %-5s\n",
                  i, cs.cs_hwid, state,
                  intr, ts, ibuf);
       }
}

int
aprint_normal(const char *fmt, ...)
{
       va_list ap;
       int rv;

       va_start(ap, fmt);
       rv = vfprintf(stdout, fmt, ap);
       va_end(ap);

       return rv;
}
__strong_alias(aprint_verbose,aprint_normal)
__strong_alias(aprint_error,aprint_normal)

int
aprint_normal_dev(const char *dev, const char *fmt, ...)
{
       va_list ap;
       int rv;

       printf("%s: ", dev);
       va_start(ap, fmt);
       rv = vfprintf(stdout, fmt, ap);
       va_end(ap);

       return rv;
}
__strong_alias(aprint_verbose_dev,aprint_normal_dev)
__strong_alias(aprint_error_dev,aprint_normal_dev)
__strong_alias(aprint_debug_dev,aprint_normal_dev)