xref: /illumos-gate/usr/src/cmd/ahciem/ahciem.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
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