1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  *  PICL Ontario platform plug-in to message the SC to light
30  *  or extinguish the hdd 'OK2RM' ready-to-service light in
31  *  the event of a soft unconfigure or configure, respectively.
32  */
33 
34 #include <picl.h>
35 #include <picltree.h>
36 #include <picldefs.h>
37 #include <stdio.h>
38 #include <umem.h>
39 #include <unistd.h>
40 #include <libnvpair.h>
41 #include <strings.h>
42 #include <syslog.h>
43 #include <dlfcn.h>
44 #include <link.h>
45 #include <signal.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <fcntl.h>
49 #include <sys/param.h>
50 #include <sys/raidioctl.h>
51 #include <libpcp.h>
52 #include "piclsbl.h"
53 
54 #include "errno.h"
55 
56 #pragma init(piclsbl_register)
57 
58 static void *pcp_handle;
59 
60 static char hba_devctl[MAXPATHLEN];
61 
62 static int (* pcp_init_ptr)();
63 static int (* pcp_send_recv_ptr)();
64 static int (* pcp_close_ptr)();
65 
66 static int load_pcp_libs(void);
67 static void piclsbl_init(void);
68 static void piclsbl_fini(void);
69 static void piclsbl_register(void);
70 static void piclsbl_handler(const char *ename, const void *earg,
71 				size_t size, void *cookie);
72 
73 static picld_plugin_reg_t piclsbl_reg = {
74 	PICLD_PLUGIN_VERSION_1,
75 	PICLD_PLUGIN_CRITICAL,
76 	"piclsbl",
77 	piclsbl_init,
78 	piclsbl_fini
79 };
80 
81 /*
82  * called from init to load the pcp library
83  */
84 static int
85 load_pcp_libs()
86 {
87 	char pcp_dl_lib[80];
88 
89 	(void) snprintf(pcp_dl_lib, sizeof (pcp_dl_lib), "%s%s",
90 				LIB_PCP_PATH, PCPLIB);
91 
92 	/* load the library and set up function pointers */
93 	if ((pcp_handle = dlopen(pcp_dl_lib, RTLD_NOW)) == (void *) NULL)
94 		return (1);
95 
96 	pcp_init_ptr = (int(*)())dlsym(pcp_handle, "pcp_init");
97 	pcp_close_ptr = (int(*)())dlsym(pcp_handle, "pcp_close");
98 	pcp_send_recv_ptr = (int(*)())dlsym(pcp_handle, "pcp_send_recv");
99 
100 	if (pcp_init_ptr == NULL || pcp_send_recv_ptr == NULL ||
101 		pcp_close_ptr == NULL)
102 		return (1);
103 
104 	return (0);
105 }
106 
107 /*
108  * callback routine for ptree_walk_tree_by_class()
109  */
110 static int
111 cb_find_disk(picl_nodehdl_t node, void *args)
112 {
113 	disk_lookup_t *lookup  = (disk_lookup_t *)args;
114 	int status = -1;
115 	char *n;
116 	char path[PICL_PROPNAMELEN_MAX];
117 
118 	status = ptree_get_propval_by_name(node, "Path", (void *)&path,
119 	    PICL_PROPNAMELEN_MAX);
120 	if (status != PICL_SUCCESS) {
121 		return (PICL_WALK_CONTINUE);
122 	}
123 
124 	if (strcmp(path, lookup->path) == 0) {
125 		lookup->disk = node;
126 		lookup->result = DISK_FOUND;
127 
128 		/* store the HBA's device path for use in check_raid() */
129 		n = strstr(path, "/sd");
130 		strncpy(n, "\0", 1);
131 		(void) snprintf(hba_devctl, MAXPATHLEN, "/devices%s:devctl",
132 				path);
133 
134 		return (PICL_WALK_TERMINATE);
135 	}
136 
137 	return (PICL_WALK_CONTINUE);
138 }
139 
140 /*
141  * check a target for RAID membership
142  */
143 static int
144 check_raid(int target)
145 {
146 	raid_config_t config;
147 	int fd;
148 	int numvols;
149 	int i;
150 	int j;
151 
152 	/*
153 	 * hba_devctl is set to the onboard hba, so it will
154 	 * always house any onboard RAID volumes
155 	 */
156 	if ((fd = open(hba_devctl, O_RDONLY)) < 0) {
157 		syslog(LOG_ERR, "%s", strerror(errno));
158 		return (0);
159 	}
160 
161 	/*
162 	 * look up the RAID configurations for the onboard
163 	 * HBA and check target against all member targets
164 	 */
165 	if (ioctl(fd, RAID_NUMVOLUMES, &numvols)) {
166 		syslog(LOG_ERR, "%s", strerror(errno));
167 		(void) close(fd);
168 		return (0);
169 	}
170 
171 	for (i = 0; i < numvols; i++) {
172 		config.unitid = i;
173 		if (ioctl(fd, RAID_GETCONFIG, &config)) {
174 			syslog(LOG_ERR, "%s", strerror(errno));
175 			(void) close(fd);
176 			return (0);
177 		}
178 
179 		for (j = 0; j < config.ndisks; j++) {
180 			if (config.disk[j] == target) {
181 				(void) close(fd);
182 				return (1);
183 			}
184 		}
185 	}
186 	(void) close(fd);
187 	return (0);
188 }
189 
190 /*
191  * Ontario SBL event handler, subscribed to:
192  * 	PICLEVENT_SYSEVENT_DEVICE_ADDED
193  * 	PICLEVENT_SYSEVENT_DEVICE_REMOVED
194  */
195 static void
196 piclsbl_handler(const char *ename, const void *earg, size_t size,
197 		void *cookie)
198 {
199 	char		*devfs_path;
200 	char		hdd_location[PICL_PROPNAMELEN_MAX];
201 	nvlist_t	*nvlp = NULL;
202 	pcp_msg_t	send_msg;
203 	pcp_msg_t	recv_msg;
204 	pcp_sbl_req_t	*req_ptr = NULL;
205 	pcp_sbl_resp_t	*resp_ptr = NULL;
206 	int		status = -1;
207 	int		target;
208 	disk_lookup_t	lookup;
209 	int		channel_fd;
210 
211 	/*
212 	 * setup the request data to attach to the libpcp msg
213 	 */
214 	if ((req_ptr = (pcp_sbl_req_t *)umem_zalloc(sizeof (pcp_sbl_req_t),
215 			UMEM_DEFAULT)) == NULL)
216 		goto sbl_return;
217 
218 	/*
219 	 * This plugin serves to enable or disable the blue RAS
220 	 * 'ok-to-remove' LED that is on each of the 4 disks on the
221 	 * Ontario.  We catch the event via the picl handler, and
222 	 * if the event is DEVICE_ADDED for one of our onboard disks,
223 	 * then we'll be turning off the LED. Otherwise, if the event
224 	 * is DEVICE_REMOVED, then we turn it on.
225 	 */
226 	if (strcmp(ename, PICLEVENT_SYSEVENT_DEVICE_ADDED) == 0)
227 		req_ptr->sbl_action = PCP_SBL_DISABLE;
228 	else if (strcmp(ename, PICLEVENT_SYSEVENT_DEVICE_REMOVED) == 0)
229 		req_ptr->sbl_action = PCP_SBL_ENABLE;
230 	else
231 		goto sbl_return;
232 
233 	/*
234 	 * retrieve the device's physical path from the event payload
235 	 */
236 	if (nvlist_unpack((char *)earg, size, &nvlp, NULL))
237 		goto sbl_return;
238 	if (nvlist_lookup_string(nvlp, "devfs-path", &devfs_path))
239 		goto sbl_return;
240 
241 	/*
242 	 * look for this disk in the picl tree, and if it's
243 	 * location indicates that it's one of our internal
244 	 * disks, then set sbl_id to incdicate which one.
245 	 * otherwise, return as it is not one of our disks.
246 	 */
247 	lookup.path = strdup(devfs_path);
248 	lookup.disk = NULL;
249 	lookup.result = DISK_NOT_FOUND;
250 
251 	/* first, find the disk */
252 	status = ptree_walk_tree_by_class(root_node, "disk", (void *)&lookup,
253 						cb_find_disk);
254 	if (status != PICL_SUCCESS)
255 		goto sbl_return;
256 
257 	if (lookup.result == DISK_FOUND) {
258 		/* now, lookup it's location in the node */
259 		status = ptree_get_propval_by_name(lookup.disk, "Location",
260 				(void *)&hdd_location, PICL_PROPNAMELEN_MAX);
261 		if (status != PICL_SUCCESS) {
262 			syslog(LOG_ERR, "piclsbl: failed hdd discovery");
263 			goto sbl_return;
264 		}
265 	}
266 
267 	/*
268 	 * Strip off the target from the NAC name.
269 	 * The disk NAC will always be HDD#
270 	 */
271 	if (strncmp(hdd_location, NAC_DISK_PREFIX,
272 		strlen(NAC_DISK_PREFIX)) == 0) {
273 			(void) sscanf(hdd_location, "%*3s%d", &req_ptr->sbl_id);
274 			target = (int)req_ptr->sbl_id;
275 	} else {
276 			/* this is not one of the onboard disks */
277 			goto sbl_return;
278 	}
279 
280 	/*
281 	 * check the onboard RAID configuration for this disk. if it is
282 	 * a member of a RAID and is not the RAID itself, ignore the event
283 	 */
284 	if (check_raid(target))
285 		goto sbl_return;
286 
287 	/*
288 	 * we have the information we need, init the platform channel.
289 	 * the platform channel driver will only allow one connection
290 	 * at a time on this socket. on the offchance that more than
291 	 * one event comes in, we'll retry to initialize this connection
292 	 * up to 3 times
293 	 */
294 	if ((channel_fd = (*pcp_init_ptr)(LED_CHANNEL)) < 0) {
295 		/* failed to init; wait and retry up to 3 times */
296 		int s = PCPINIT_TIMEOUT;
297 		int retries = 0;
298 		while (++retries) {
299 			(void) sleep(s);
300 			if ((channel_fd = (*pcp_init_ptr)(LED_CHANNEL)) >= 0)
301 				break;
302 			else if (retries == 3) {
303 				syslog(LOG_ERR, "piclsbl: ",
304 					"SC channel initialization failed");
305 				goto sbl_return;
306 			}
307 			/* continue */
308 		}
309 	}
310 
311 	/*
312 	 * populate the message for libpcp
313 	 */
314 	send_msg.msg_type = PCP_SBL_CONTROL;
315 	send_msg.sub_type = NULL;
316 	send_msg.msg_len = sizeof (pcp_sbl_req_t);
317 	send_msg.msg_data = (uint8_t *)req_ptr;
318 
319 	/*
320 	 * send the request, receive the response
321 	 */
322 	if ((*pcp_send_recv_ptr)(channel_fd, &send_msg, &recv_msg,
323 		PCPCOMM_TIMEOUT) < 0) {
324 		/* we either timed out or erred; either way try again */
325 		int s = PCPCOMM_TIMEOUT;
326 		(void) sleep(s);
327 		if ((*pcp_send_recv_ptr)(channel_fd, &send_msg, &recv_msg,
328 				PCPCOMM_TIMEOUT) < 0) {
329 			syslog(LOG_ERR, "piclsbl: communication failure");
330 			goto sbl_return;
331 		}
332 	}
333 
334 	/*
335 	 * validate that this data was meant for us
336 	 */
337 	if (recv_msg.msg_type != PCP_SBL_CONTROL_R) {
338 		syslog(LOG_ERR, "piclsbl: unbound packet received");
339 		goto sbl_return;
340 	}
341 
342 	/*
343 	 * verify that the LED action has taken place
344 	 */
345 	resp_ptr = (pcp_sbl_resp_t *)recv_msg.msg_data;
346 	if (resp_ptr->status == PCP_SBL_ERROR) {
347 		syslog(LOG_ERR, "piclsbl: OK2RM LED action error");
348 		goto sbl_return;
349 	}
350 
351 	/*
352 	 * ensure the LED action taken is the one requested
353 	 */
354 	if ((req_ptr->sbl_action == PCP_SBL_DISABLE) &&
355 		(resp_ptr->sbl_state != SBL_STATE_OFF))
356 		syslog(LOG_ERR, "piclsbl: OK2RM LED not OFF after disk "
357 				"configuration");
358 	else if ((req_ptr->sbl_action == PCP_SBL_ENABLE) &&
359 			(resp_ptr->sbl_state != SBL_STATE_ON))
360 		syslog(LOG_ERR, "piclsbl: OK2RM LED not ON after disk "
361 				"unconfiguration");
362 	else if (resp_ptr->sbl_state == SBL_STATE_UNKNOWN)
363 		syslog(LOG_ERR, "piclsbl: OK2RM LED set to unknown state");
364 
365 sbl_return:
366 
367 	(*pcp_close_ptr)(channel_fd);
368 	if (req_ptr != NULL)
369 		umem_free(req_ptr, sizeof (pcp_sbl_req_t));
370 	if (resp_ptr != NULL)
371 		free(resp_ptr);
372 	if (nvlp != NULL)
373 		nvlist_free(nvlp);
374 }
375 
376 static void
377 piclsbl_init(void)
378 {
379 	/* retrieve the root node for lookups in the event handler */
380 	if ((ptree_get_root(&root_node)) != NULL)
381 		return;
382 
383 	/* load libpcp */
384 	if (load_pcp_libs()) {
385 		syslog(LOG_ERR, "piclsbl: failed to load libpcp");
386 		syslog(LOG_ERR, "piclsbl: aborting");
387 		return;
388 	}
389 
390 	/*
391 	 * register piclsbl_handler for both "sysevent-device-added" and
392 	 * and for "sysevent-device-removed" PICL events
393 	 */
394 	(void) ptree_register_handler(PICLEVENT_SYSEVENT_DEVICE_ADDED,
395 	    piclsbl_handler, NULL);
396 	(void) ptree_register_handler(PICLEVENT_SYSEVENT_DEVICE_REMOVED,
397 	    piclsbl_handler, NULL);
398 }
399 
400 static void
401 piclsbl_fini(void)
402 {
403 	/* unregister the event handler */
404 	(void) ptree_unregister_handler(PICLEVENT_SYSEVENT_DEVICE_ADDED,
405 	    piclsbl_handler, NULL);
406 	(void) ptree_unregister_handler(PICLEVENT_SYSEVENT_DEVICE_REMOVED,
407 	    piclsbl_handler, NULL);
408 }
409 
410 static void
411 piclsbl_register(void)
412 {
413 	picld_plugin_register(&piclsbl_reg);
414 }
415