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 } 303