/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
*/
#include <libgen.h>
#include <sys/scsi/targets/sesio.h>
#include <sys/scsi/impl/uscsi.h>
#include <sys/scsi/generic/commands.h>
#include <cfga_scsi.h>
#define AMBER_LED_ON 1
#define AMBER_LED_BLINK 2
#define AMBER_LED_OFF 3
#define SESRETRY 3 /* try 3 times */
#define SESIOCWAIT 1 /* wait for 1s */
#define SES_PATH "/dev/es/"
#define SCSZ 0x4cc
#define PLATSES_LOCATOR_OPT 0
#define PLATSES_LED_OPT 1
#define PLATSES_MODE_OPT 2
char *platses_opts[] = {
"locator",
"led",
"mode",
NULL
};
struct info_disk {
int object_id; /* object id of disk for ses_ioctl(obj_id) */
char name[255]; /* sas_address(wwn) of disk */
};
/* CDB for Additional Element Status diagnositc page(page code:0xa) */
static char cdb[CDB_GROUP0] = {SCMD_GDIAG, 0x1, 0xa, (SCSZ)>>8,
(char)((SCSZ) & 0xff), 0};
static scfga_ret_t platses_setled(const char *mode, apid_t *apidp,
char **errstring, struct cfga_msg *msgp);
static scfga_ret_t platses_getled(int print_switch, apid_t *apidp,
char **errstring, struct cfga_msg *msgp);
static scfga_ret_t platses_setlocator(const char *mode, apid_t *apidp,
char **errstring, struct cfga_msg *msgp);
static void platses_print_led(apid_t *apidp, struct cfga_msg *msgp,
ses_objarg target_disk);
static void platses_print_locator(apid_t *apidp, struct cfga_msg *msgp,
ses_objarg target_disk);
static scfga_ret_t platses_disk_led_control(apid_t *apidp, char **errstring,
struct cfga_msg *msgp, int request, ses_objarg *target_disk);
static int get_led_status(ses_objarg target_disk);
/*
* get the status of led from target_disk.
*/
static int
get_led_status(ses_objarg target_disk)
{
int led = -1;
if (((target_disk.cstat[2] & SESCTL_RQSID) == 0) &&
(target_disk.cstat[3] & SESCTL_RQSFLT) == 0) {
led = AMBER_LED_OFF;
} else {
if (((target_disk.cstat[2] & SESCTL_RQSID) != 0) &&
(target_disk.cstat[3] & SESCTL_RQSFLT) == 0) {
led = AMBER_LED_BLINK;
} else {
if (((target_disk.cstat[2] & SESCTL_RQSID) == 0) &&
(target_disk.cstat[3] & SESCTL_RQSFLT) != 0) {
led = AMBER_LED_ON;
} else {
led = -1;
}
}
}
return (led);
}
/*
* Use ses driver to get all the disks' information.
* Use the information of disk to set or get the LED status.
*
* Returns 0 on success.
*/
static scfga_ret_t
platses_disk_led_control(apid_t *apidp, char **errstring, struct cfga_msg *msgp,
int request, ses_objarg *target_disk)
{
int ses_fd = -1;
int retry = 0;
int i, j;
char *phy_disk_path;
char *wwn_disk;
char *tmp_wwn_disk;
char phy_ses_path[255] = "\0";
int cmp = '/';
ses_objarg tmp_target_disk;
DIR *dir;
struct dirent *dp;
char *obj;
struct uscsi_cmd *uscsicmdp;
struct info_disk *get_disk;
int cmp_rtn = -2;
if ((apidp == NULL) || (msgp == NULL) || (target_disk == NULL)) {
cfga_err(errstring, 0, ERR_UNKNOWN, 0, 0);
return (SCFGA_ERR);
}
/*
* get the wwn_disk and phys_ses_path from apidp->path
*/
phy_disk_path = strdup(apidp->path);
/*
* /devices/pci@1,700000/LSI,sas@0/iport@f/disk@w500000e1143dbc92,0
* get the char from w and before ","
* result likes this:500000e1143dbc92
* this is the wwn of disk(sas_address)
*/
tmp_wwn_disk = strrchr(phy_disk_path, cmp);
wwn_disk = strtok((strpbrk(tmp_wwn_disk, "w") + 1), ",");
/*
* set uscsi command parameter
*/
obj = calloc(1, SCSZ);
uscsicmdp = calloc(1, sizeof (struct uscsi_cmd));
uscsicmdp->uscsi_flags = USCSI_READ;
uscsicmdp->uscsi_cdb = cdb;
uscsicmdp->uscsi_cdblen = CDB_GROUP0;
uscsicmdp->uscsi_buflen = SCSZ;
uscsicmdp->uscsi_bufaddr = obj;
/*
* search under the /dev/es/
*/
if ((dir = opendir(SES_PATH)) == NULL) {
free(obj);
free(uscsicmdp);
return (SCFGA_OPNOTSUPP);
}
for (dp = readdir(dir); dp != NULL; dp = readdir(dir)) {
char tmp_ses_dir[255] = SES_PATH;
int nobj;
ses_object *objmap;
int hdd_num = 0;
int aes_hdd_num = 0;
char *aes_hdr;
char *aes_tail;
ushort_t aes_len;
char *aes_desc;
uchar_t desc_len;
char *slot_elem;
uchar_t phy_desc_num;
char *phy_desc;
char *sas_addr;
(void) strncat(tmp_ses_dir, dp->d_name, strlen(dp->d_name));
ses_fd = open(tmp_ses_dir, O_RDWR);
if (ses_fd == -1)
continue;
/*
* count HDD element
*/
if (ioctl(ses_fd, SESIOC_GETNOBJ, &nobj) < 0) {
(void) close(ses_fd);
continue;
}
objmap = calloc(1, (sizeof (ses_object) * nobj));
if (objmap == NULL) {
(void) close(ses_fd);
continue;
}
if (ioctl(ses_fd, SESIOC_GETOBJMAP, objmap) < 0) {
free(objmap);
(void) close(ses_fd);
continue;
}
for (i = 0; i < nobj; i++) {
if (objmap[i].elem_type == SESTYP_ARRAY) {
hdd_num++;
}
}
if (hdd_num == 0) {
free(objmap);
(void) close(ses_fd);
continue;
}
get_disk = calloc(1, (sizeof (struct info_disk) * hdd_num));
/*
* assgin object id
*/
j = 0;
for (i = 0; i < nobj; i++) {
if (objmap[i].elem_type != SESTYP_ARRAY) {
continue;
}
get_disk[j].object_id = i;
j++;
}
/*
* get AES diag page by uscsi
*/
errno = 0;
retry = 0;
while (ioctl(ses_fd, USCSICMD, uscsicmdp) < 0) {
/* Check Retry Error Number */
if (errno != EBUSY && errno != EIO) {
break;
}
/* Check Retry Times */
if (++retry > SESRETRY) {
break;
}
errno = 0;
(void) sleep(SESIOCWAIT);
}
if ((errno != 0) || (retry > SESRETRY)) {
free(objmap);
free(get_disk);
(void) close(ses_fd);
continue;
}
/*
* walk AES diag page and get sas_addr
*/
aes_hdr = uscsicmdp->uscsi_bufaddr;
/* aes_len is whole length of AES page */
aes_len = (ushort_t)(aes_hdr[2] << 8 | aes_hdr[3]) + 4;
aes_tail = aes_hdr + aes_len;
aes_desc = aes_hdr + 8;
while (aes_desc < aes_tail) {
/*
* check AES descriptor
*/
desc_len = aes_desc[1];
if (desc_len == 0) {
/* can't walk */
break;
}
/* EIP=1 and PROTOCOL ID=6(SAS) is supported */
if ((aes_desc[0] & 0x10) == 0 ||
(aes_desc[0] & 0x0f) != 6) {
aes_desc += (desc_len + 2);
continue;
}
/*
* check element
*/
slot_elem = aes_desc + 4;
/* DESCRIPTOR TYPE=0(Device Slot) is supported */
if ((slot_elem[1] & 0xc0) != 0) {
/* skip to next AES descriptor */
aes_desc += (desc_len + 2);
continue;
}
phy_desc_num = (uchar_t)slot_elem[0];
for (i = 0; i < phy_desc_num; i++) {
phy_desc = slot_elem + 4 + (28 * i);
sas_addr = phy_desc + 12;
(void) sprintf(get_disk[aes_hdd_num].name,
"%016llx",
(u_longlong_t)SCSI_READ64(sas_addr));
}
aes_hdd_num++;
aes_desc += (desc_len + 2);
}
if (aes_hdd_num == 0 || aes_hdd_num != hdd_num) {
free(objmap);
free(get_disk);
(void) close(ses_fd);
continue;
}
free(objmap);
/*
* search sas_addr and get object id
*/
for (i = 0; i < hdd_num; i++) {
cmp_rtn = strncmp(wwn_disk, get_disk[i].name,
strlen(wwn_disk));
if (cmp_rtn == 0) {
target_disk->obj_id = get_disk[i].object_id;
break;
}
}
if (cmp_rtn == 0) {
(void) strncpy(phy_ses_path, tmp_ses_dir,
strlen(tmp_ses_dir));
free(get_disk);
(void) close(ses_fd);
break;
}
free(get_disk);
(void) close(ses_fd);
}
(void) closedir(dir);
free(obj);
free(uscsicmdp);
if (cmp_rtn != 0) {
return (SCFGA_OPNOTSUPP);
}
/*
* If the cmd is "set led/locator", get the status of led at first
*/
if (request == SESIOC_SETOBJSTAT) {
(void) memset((void *)&tmp_target_disk, 0,
sizeof (tmp_target_disk));
tmp_target_disk.obj_id = target_disk->obj_id;
ses_fd = open(phy_ses_path, O_RDWR);
if (ses_fd < 0) {
cfga_err(errstring, errno, ERRARG_OPEN,
phy_ses_path, 0);
return (SCFGA_LIB_ERR);
}
errno = 0;
while (ioctl(ses_fd, SESIOC_GETOBJSTAT, &tmp_target_disk) < 0) {
/* Check Retry Error Number */
if (errno != EBUSY && errno != EIO) {
break;
}
/* Check Retry Times */
if (++retry > SESRETRY) {
break;
}
errno = 0;
(void) sleep(SESIOCWAIT);
}
(void) close(ses_fd);
if ((errno != 0) || (retry > SESRETRY)) {
cfga_err(errstring, errno, ERR_OP_FAILED, 0, 0);
}
if ((tmp_target_disk.cstat[2] & SESCTL_RQSRMV) != 0) {
target_disk->cstat[2] = target_disk->cstat[2] |
SESCTL_RQSRMV;
}
}
/*
* Use the ioctl interface with the ses driver to get/set the
* hdd locator indicator.
*/
ses_fd = open(phy_ses_path, O_RDWR);
if (ses_fd < 0) {
cfga_err(errstring, errno, ERRARG_OPEN, phy_ses_path, 0);
return (SCFGA_LIB_ERR);
}
errno = 0;
while (ioctl(ses_fd, request, target_disk) < 0) {
/* Check Retry Error Number */
if (errno != EBUSY && errno != EIO) {
break;
}
/* Check Retry Times */
if (++retry > SESRETRY) {
break;
}
errno = 0;
(void) sleep(SESIOCWAIT);
}
(void) close(ses_fd);
if ((errno != 0) || (retry > SESRETRY)) {
cfga_err(errstring, errno, ERR_OP_FAILED, 0, 0);
return (SCFGA_LIB_ERR);
}
return (SCFGA_OK);
}
/*
* Print the value of the hard disk locator in a human friendly form.
*/
static void
platses_print_locator(apid_t *apidp, struct cfga_msg *msgp,
ses_objarg target_disk)
{
led_modeid_t mode = LED_MODE_UNK;
int led = -1;
if ((msgp == NULL) || (msgp->message_routine == NULL)) {
return;
}
led = get_led_status(target_disk);
cfga_msg(msgp, MSG_LED_HDR, 0);
switch (led) {
case AMBER_LED_ON:
mode = LED_MODE_FAULTED;
break;
case AMBER_LED_OFF:
mode = LED_MODE_OFF;
break;
case AMBER_LED_BLINK:
mode = LED_MODE_ON;
break;
default:
mode = LED_MODE_UNK;
}
cfga_led_msg(msgp, apidp, LED_STR_LOCATOR, mode);
}
/*
* Print the value of the hard disk fault LED in a human friendly form.
*/
static void
platses_print_led(apid_t *apidp, struct cfga_msg *msgp, ses_objarg target_disk)
{
led_modeid_t mode = LED_MODE_UNK;
int led = -1;
if ((msgp == NULL) || (msgp->message_routine == NULL)) {
return;
}
led = get_led_status(target_disk);
cfga_msg(msgp, MSG_LED_HDR, 0);
switch (led) {
case AMBER_LED_ON:
mode = LED_MODE_ON;
break;
case AMBER_LED_OFF:
mode = LED_MODE_OFF;
break;
case AMBER_LED_BLINK:
mode = LED_MODE_BLINK;
break;
default:
mode = LED_MODE_UNK;
}
cfga_led_msg(msgp, apidp, LED_STR_FAULT, mode);
}
static scfga_ret_t
platses_setlocator(
const char *mode,
apid_t *apidp,
char **errstring,
struct cfga_msg *msgp)
{
scfga_ret_t retval;
ses_objarg target_disk;
(void) memset((void *)&target_disk, 0, sizeof (target_disk));
if (strcmp(mode, "on") == 0) {
target_disk.cstat[0] = SESCTL_CSEL;
target_disk.cstat[1] = 0x00;
target_disk.cstat[2] = SESCTL_RQSID;
target_disk.cstat[3] = 0x00;
} else if (strcmp(mode, "off") == 0) {
target_disk.cstat[0] = SESCTL_CSEL;
target_disk.cstat[1] = 0x00;
target_disk.cstat[2] = 0x00;
target_disk.cstat[3] = 0x00;
} else {
cfga_err(errstring, 0, ERRARG_OPT_INVAL, mode, 0);
return (SCFGA_ERR);
}
retval = platses_disk_led_control(apidp, errstring, msgp,
SESIOC_SETOBJSTAT, &target_disk);
return (retval);
}
static scfga_ret_t
platses_getled(
int print_switch,
apid_t *apidp,
char **errstring,
struct cfga_msg *msgp)
{
scfga_ret_t retval;
ses_objarg target_disk;
(void) memset((void *)&target_disk, 0, sizeof (target_disk));
retval = platses_disk_led_control(apidp, errstring, msgp,
SESIOC_GETOBJSTAT, &target_disk);
if (retval != SCFGA_OK) {
return (retval);
}
if (print_switch == PLATSES_LED_OPT) {
platses_print_led(apidp, msgp, target_disk);
} else {
platses_print_locator(apidp, msgp, target_disk);
}
return (SCFGA_OK);
}
static scfga_ret_t
platses_setled(
const char *mode,
apid_t *apidp,
char **errstring,
struct cfga_msg *msgp)
{
scfga_ret_t retval;
ses_objarg target_disk;
(void) memset((void *)&target_disk, 0, sizeof (target_disk));
if (strcmp(mode, "on") == 0) {
target_disk.cstat[0] = SESCTL_CSEL;
target_disk.cstat[1] = 0x00;
target_disk.cstat[2] = 0x00;
target_disk.cstat[3] = SESCTL_RQSFLT;
} else if (strcmp(mode, "off") == 0) {
target_disk.cstat[0] = SESCTL_CSEL;
target_disk.cstat[1] = 0x00;
target_disk.cstat[2] = 0x00;
target_disk.cstat[3] = 0x00;
} else if (strcmp(mode, "blink") == 0) {
target_disk.cstat[0] = SESCTL_CSEL;
target_disk.cstat[1] = 0x00;
target_disk.cstat[2] = SESCTL_RQSID;
target_disk.cstat[3] = 0x00;
} else {
cfga_err(errstring, 0, ERRARG_OPT_INVAL, mode, 0);
return (SCFGA_ERR);
}
retval = platses_disk_led_control(apidp, errstring, msgp,
SESIOC_SETOBJSTAT, &target_disk);
return (retval);
}
/*
* The func argument is a string in one of the two following forms:
* led=LED[,mode=MODE]
* locator[=on|off]
* which can generically be thought of as:
* name=value[,name=value]
* So first, split the function based on the comma into two name-value
* pairs.
*
* start with plat_dev_led, and use ioctl to get/set led.
*/
/*ARGSUSED*/
scfga_ret_t
plat_dev_led(
const char *func,
scfga_cmd_t cmd,
apid_t *apidp,
prompt_t *argsp,
cfga_flags_t flags,
char **errstring)
{
scfga_ret_t retval = SCFGA_ERR;
char *optptr = (char *)func;
char *value = NULL;
int opt_locator = 0;
int opt_led = 0;
int opt_mode = 0;
char *locator_value = NULL;
char *led_value = NULL;
char *mode_value = NULL;
FILE *fp;
char tmp_info[255];
char *phy_disk_path;
int cmp = '/';
char *tmp_wwn_disk;
DIR *dir;
struct dirent *dp;
int ses_fd = -1;
int open_flag = 0;
fp = popen("/usr/sbin/prtconf -b | /usr/bin/grep ORCL,SPARC64", "r");
if (fgets(tmp_info, sizeof (tmp_info), fp) == NULL) {
(void) pclose(fp);
cfga_err(errstring, 0, ERR_UNAVAILABLE, 0);
return (SCFGA_ERR);
}
(void) pclose(fp);
/*
* check if the ses device is accessible
*/
if ((dir = opendir(SES_PATH)) == NULL) {
return (SCFGA_OPNOTSUPP);
}
for (dp = readdir(dir); dp != NULL; dp = readdir(dir)) {
char tmp_ses_dir[255] = SES_PATH;
(void) strncat(tmp_ses_dir, dp->d_name, strlen(dp->d_name));
ses_fd = open(tmp_ses_dir, O_RDWR);
if (ses_fd != -1) {
open_flag = 1;
(void) close(ses_fd);
}
}
(void) closedir(dir);
if (open_flag == 0) {
return (SCFGA_OPNOTSUPP);
}
phy_disk_path = strdup(apidp->path);
tmp_wwn_disk = strrchr(phy_disk_path, cmp);
if (strpbrk(tmp_wwn_disk, "w") == NULL) {
return (SCFGA_OPNOTSUPP);
}
if (func == NULL) {
return (SCFGA_ERR);
}
while (*optptr != '\0') {
switch (getsubopt(&optptr, platses_opts, &value)) {
case PLATSES_LOCATOR_OPT:
opt_locator++;
locator_value = value;
break;
case PLATSES_LED_OPT:
opt_led++;
led_value = value;
break;
case PLATSES_MODE_OPT:
opt_mode++;
mode_value = value;
break;
default:
cfga_err(errstring, 0, ERR_CMD_INVAL, 0);
return (SCFGA_OPNOTSUPP);
break;
}
}
/*
* In cmd, there is no locator or led, return err.
*/
if (!opt_locator && !opt_led) {
cfga_err(errstring, 0, ERR_CMD_INVAL, 0);
return (SCFGA_ERR);
}
/*
* For locator
*/
if (opt_locator) {
if ((opt_locator > 1) || opt_led || opt_mode ||
(strncmp(func, "locator", strlen("locator")) != 0) ||
(locator_value &&
(strcmp(locator_value, "blink") == 0))) {
cfga_err(errstring, 0, ERR_CMD_INVAL, 0);
return (SCFGA_ERR);
}
/* Options are sane so set or get the locator. */
if (locator_value) {
retval = platses_setlocator(locator_value, apidp,
errstring, argsp->msgp);
} else {
retval = platses_getled(PLATSES_LOCATOR_OPT, apidp,
errstring, argsp->msgp);
}
}
if (opt_led) {
if ((opt_led > 1) || (opt_mode > 1) || (opt_locator) ||
(strncmp(func, "led", strlen("led")) != 0) ||
(led_value &&
(strcmp(led_value, "fault") != 0)) ||
(opt_mode && !mode_value)) {
cfga_err(errstring, 0, ERR_CMD_INVAL, 0);
return (SCFGA_ERR);
}
/* options are sane so go ahead and set or get the led */
if (mode_value != NULL) {
retval = platses_setled(mode_value, apidp, errstring,
argsp->msgp);
} else {
retval = platses_getled(PLATSES_LED_OPT, apidp,
errstring, argsp->msgp);
}
}
return (retval);
}