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
ahciem_usage(const char * fmt,...)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 *
ahciem_led_to_string(uint_t led)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
ahciem_match(ahciem_t * ahci,const char * port)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
ahciem_parse(const char * arg)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
ahciem_set(ahciem_t * ahci,const char * portstr,int fd,int port)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
ahciem_devinfo(di_node_t node,void * arg)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
main(int argc,char * argv[])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