1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright (c) 2018 Joyent, Inc.
  14  */
  15 
  16 #include <stdio.h>
  17 #include <sys/types.h>
  18 #include <sys/stat.h>
  19 #include <fcntl.h>
  20 #include <errno.h>
  21 #include <string.h>
  22 #include <strings.h>
  23 #include <unistd.h>
  24 #include <stdlib.h>
  25 #include <err.h>
  26 #include <libgen.h>
  27 #include <libdevinfo.h>
  28 
  29 #include <sys/sata/adapters/ahci/ahciem.h>
  30 
  31 #define AHCIEM_IDENT    "ident"
  32 #define AHCIEM_FAULT    "fault"
  33 #define AHCIEM_NOACTIVITY       "noactivity"
  34 #define AHCIEM_DEFAULT  "default"
  35 #define AHCIEM_UNKNOWN  "unknown"
  36 
  37 #define EXIT_USAGE      2
  38 
  39 static const char *ahciem_progname;
  40 
  41 typedef struct {
  42         boolean_t               ahci_set;
  43         ahci_em_led_state_t     ahci_led;
  44         int                     ahci_argc;
  45         char                    **ahci_argv;
  46         boolean_t               *ahci_found;
  47         int                     ahci_err;
  48 } ahciem_t;
  49 
  50 static void
  51 ahciem_usage(const char *fmt, ...)
  52 {
  53         if (fmt != NULL) {
  54                 va_list ap;
  55 
  56                 va_start(ap, fmt);
  57                 vwarnx(fmt, ap);
  58                 va_end(ap);
  59         }
  60 
  61         (void) fprintf(stderr, "Usage: %s [-s mode] [port]\n"
  62             "\n"
  63             "\t-s mode\t\tset LED to mode\n",
  64             ahciem_progname);
  65 }
  66 
  67 static const char *
  68 ahciem_led_to_string(uint_t led)
  69 {
  70         switch (led) {
  71         case AHCI_EM_LED_IDENT_ENABLE:
  72                 return (AHCIEM_IDENT);
  73         case AHCI_EM_LED_FAULT_ENABLE:
  74                 return (AHCIEM_FAULT);
  75         case AHCI_EM_LED_ACTIVITY_DISABLE:
  76                 return (AHCIEM_NOACTIVITY);
  77         case (AHCI_EM_LED_IDENT_ENABLE | AHCI_EM_LED_FAULT_ENABLE):
  78                 return (AHCIEM_IDENT "," AHCIEM_FAULT);
  79         case (AHCI_EM_LED_IDENT_ENABLE | AHCI_EM_LED_ACTIVITY_DISABLE):
  80                 return (AHCIEM_IDENT "," AHCIEM_NOACTIVITY);
  81         case (AHCI_EM_LED_FAULT_ENABLE | AHCI_EM_LED_ACTIVITY_DISABLE):
  82                 return (AHCIEM_FAULT "," AHCIEM_NOACTIVITY);
  83         /* BEGIN CSTYLED */
  84         case (AHCI_EM_LED_IDENT_ENABLE | AHCI_EM_LED_FAULT_ENABLE |
  85             AHCI_EM_LED_ACTIVITY_DISABLE):
  86                 return (AHCIEM_IDENT "," AHCIEM_FAULT "," AHCIEM_NOACTIVITY);
  87         /* END CSTYLED */
  88         case 0:
  89                 return (AHCIEM_DEFAULT);
  90         default:
  91                 return (AHCIEM_UNKNOWN);
  92         }
  93 }
  94 
  95 static boolean_t
  96 ahciem_match(ahciem_t *ahci, const char *port)
  97 {
  98         int i;
  99 
 100         if (ahci->ahci_argc == 0)
 101                 return (B_TRUE);
 102 
 103         for (i = 0; i < ahci->ahci_argc; i++) {
 104                 size_t len = strlen(ahci->ahci_argv[i]);
 105 
 106                 /*
 107                  * Perform a partial match on the base name. This allows us to
 108                  * match all of a controller by using a string like "ahci0".
 109                  */
 110                 if (strncmp(ahci->ahci_argv[i], port, len) == 0) {
 111                         ahci->ahci_found[i] = B_TRUE;
 112                         return (B_TRUE);
 113                 }
 114 
 115         }
 116 
 117         return (B_FALSE);
 118 }
 119 
 120 static ahci_em_led_state_t
 121 ahciem_parse(const char *arg)
 122 {
 123         if (strcmp(arg, AHCIEM_IDENT) == 0) {
 124                 return (AHCI_EM_LED_IDENT_ENABLE);
 125         } else if (strcmp(arg, AHCIEM_FAULT) == 0) {
 126                 return (AHCI_EM_LED_FAULT_ENABLE);
 127         } else if (strcmp(arg, AHCIEM_NOACTIVITY) == 0) {
 128                 return (AHCI_EM_LED_ACTIVITY_DISABLE);
 129         } else if (strcmp(arg, AHCIEM_DEFAULT) == 0) {
 130                 return (0);
 131         }
 132 
 133         errx(EXIT_USAGE, "invalid LED mode with -s: %s", arg);
 134 }
 135 
 136 static void
 137 ahciem_set(ahciem_t *ahci, const char *portstr, int fd, int port)
 138 {
 139         ahci_ioc_em_set_t set;
 140 
 141         bzero(&set, sizeof (set));
 142 
 143         set.aiems_port = port;
 144         set.aiems_op = AHCI_EM_IOC_SET_OP_SET;
 145         set.aiems_leds = ahci->ahci_led;
 146 
 147         if (ioctl(fd, AHCI_EM_IOC_SET, &set) != 0) {
 148                 warn("failed to set LEDs on %s", portstr);
 149                 ahci->ahci_err = 1;
 150         }
 151 }
 152 
 153 static int
 154 ahciem_devinfo(di_node_t node, void *arg)
 155 {
 156         char *driver, *mpath, *fullpath;
 157         const char *sup;
 158         int inst, fd;
 159         uint_t i;
 160         ahciem_t *ahci = arg;
 161         di_minor_t m;
 162         ahci_ioc_em_get_t get;
 163 
 164         if ((driver = di_driver_name(node)) == NULL)
 165                 return (DI_WALK_CONTINUE);
 166         if (strcmp(driver, "ahci") != 0)
 167                 return (DI_WALK_CONTINUE);
 168         inst = di_instance(node);
 169 
 170         m = DI_MINOR_NIL;
 171         while ((m = di_minor_next(node, m)) != DI_MINOR_NIL) {
 172                 char *mname = di_minor_name(m);
 173 
 174                 if (mname != NULL && strcmp("devctl", mname) == 0)
 175                         break;
 176         }
 177 
 178         if (m == DI_MINOR_NIL) {
 179                 warnx("encountered ahci%d without devctl node", inst);
 180                 return (DI_WALK_PRUNECHILD);
 181         }
 182 
 183         if ((mpath = di_devfs_minor_path(m)) == NULL) {
 184                 warnx("failed to get path for ahci%d devctl minor", inst);
 185                 return (DI_WALK_PRUNECHILD);
 186         }
 187 
 188         if (asprintf(&fullpath, "/devices/%s", mpath) == -1) {
 189                 warn("failed to construct /devices path from %s", mpath);
 190                 return (DI_WALK_PRUNECHILD);
 191         }
 192 
 193         if ((fd = open(fullpath, O_RDWR)) < 0) {
 194                 warn("failed to open ahci%d devctl path %s", inst, fullpath);
 195                 goto out;
 196         }
 197 
 198         bzero(&get, sizeof (get));
 199         if (ioctl(fd, AHCI_EM_IOC_GET, &get) != 0) {
 200                 warn("failed to get AHCI enclosure information for ahci%d",
 201                     inst);
 202                 ahci->ahci_err = 1;
 203                 goto out;
 204         }
 205 
 206         if ((get.aiemg_flags & AHCI_EM_FLAG_CONTROL_ACTIVITY) != 0) {
 207                 sup = ahciem_led_to_string(AHCI_EM_LED_IDENT_ENABLE |
 208                     AHCI_EM_LED_FAULT_ENABLE | AHCI_EM_LED_ACTIVITY_DISABLE);
 209         } else {
 210                 sup = ahciem_led_to_string(AHCI_EM_LED_IDENT_ENABLE |
 211                     AHCI_EM_LED_FAULT_ENABLE);
 212         }
 213 
 214         for (i = 0; i < AHCI_EM_IOC_MAX_PORTS; i++) {
 215                 char port[64];
 216                 const char *state;
 217 
 218                 if (((1 << i) & get.aiemg_nports) == 0)
 219                         continue;
 220 
 221                 (void) snprintf(port, sizeof (port), "ahci%d/%u", inst, i);
 222                 if (!ahciem_match(ahci, port))
 223                         continue;
 224 
 225                 if (ahci->ahci_set) {
 226                         ahciem_set(ahci, port, fd, i);
 227                         continue;
 228                 }
 229 
 230                 state = ahciem_led_to_string(get.aiemg_status[i]);
 231                 (void) printf("%-20s %-12s %s,default\n", port, state, sup);
 232         }
 233 
 234 out:
 235         free(fullpath);
 236         return (DI_WALK_PRUNECHILD);
 237 }
 238 
 239 int
 240 main(int argc, char *argv[])
 241 {
 242         int c, i, ret;
 243         di_node_t root;
 244         ahciem_t ahci;
 245 
 246         ahciem_progname = basename(argv[0]);
 247 
 248         bzero(&ahci, sizeof (ahciem_t));
 249         while ((c = getopt(argc, argv, ":s:")) != -1) {
 250                 switch (c) {
 251                 case 's':
 252                         ahci.ahci_set = B_TRUE;
 253                         ahci.ahci_led = ahciem_parse(optarg);
 254                         break;
 255                 case ':':
 256                         ahciem_usage("option -%c requires an operand\n",
 257                             optopt);
 258                         return (EXIT_USAGE);
 259                 case '?':
 260                 default:
 261                         ahciem_usage("unknown option: -%c\n", optopt);
 262                         return (EXIT_USAGE);
 263                 }
 264         }
 265 
 266         argc -= optind;
 267         argv += optind;
 268         ahci.ahci_argc = argc;
 269         ahci.ahci_argv = argv;
 270         if (argc > 0) {
 271                 ahci.ahci_found = calloc(argc, sizeof (boolean_t));
 272                 if (ahci.ahci_found == NULL) {
 273                         err(EXIT_FAILURE, "failed to alloc memory for %d "
 274                             "booleans", argc);
 275                 }
 276         }
 277 
 278         if ((root = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) {
 279                 err(EXIT_FAILURE, "failed to open devinfo tree");
 280         }
 281 
 282         if (!ahci.ahci_set) {
 283                 (void) printf("%-20s %-12s %s\n", "PORT", "ACTIVE",
 284                     "SUPPORTED");
 285         }
 286 
 287         if (di_walk_node(root, DI_WALK_CLDFIRST, &ahci,
 288             ahciem_devinfo) != 0) {
 289                 err(EXIT_FAILURE, "failed to walk devinfo tree");
 290         }
 291 
 292         ret = ahci.ahci_err;
 293         for (i = 0; i < argc; i++) {
 294                 if (ahci.ahci_found[i])
 295                         continue;
 296                 warnx("failed to find ahci enclosure port \"%s\"",
 297                     ahci.ahci_argv[i]);
 298                 ret = 1;
 299         }
 300 
 301         return (ret);
 302 }