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