/*
* Intel(R) Enclosure LED Utilities
* Copyright (C) 2009-2021 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
#include <dirent.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if _HAVE_DMALLOC_H
#include <dmalloc.h>
#endif
#include <scsi/sg_lib.h>
#include <scsi/sg_cmds_extra.h>
#include "ses.h"
#include "utils.h"
static int debug = 0;
#define ENCL_CFG_DIAG_STATUS 0x01
#define ENCL_CTRL_DIAG_STATUS 0x02
#define ENCL_CTRL_DIAG_CFG 0x02
#define ENCL_EL_DESCR_STATUS 0x07
#define ENCL_ADDITIONAL_EL_STATUS 0x0a
#define SCSI_PROTOCOL_SAS 6
static int get_ses_page(int fd, struct ses_page *p, int pg_code)
{
int ret;
int retry_count = 3;
do {
ret = sg_ll_receive_diag(fd, 1, pg_code, p->buf, sizeof(p->buf),
0, debug);
} while (ret && retry_count--);
if (!ret)
p->len = (p->buf[2] << 8) + p->buf[3] + 4;
return ret;
}
static int process_page1(struct ses_pages *sp)
{
int num_encl; /* number of subenclosures */
unsigned char *ed; /* Enclosure Descriptor */
int len = 0;
int sum_headers = 0; /* Number of Type descriptor headers */
int i = 0;
/* How many enclosures is in the main enclosure? */
num_encl = sp->page1.buf[1] + 1;
/* Go to Enclosure Descriptor */
ed = sp->page1.buf + 8;
for (i = 0; i < num_encl; i++, ed += len) {
if (ed + 3 > sp->page1.buf + sp->page1.len) {
log_debug
("SES: Error, response pare 1 truncated at %d\n",
i);
return 1;
}
sum_headers += ed[2];
len = ed[3] + 4;
if (len < 40) {
log_debug("SES: Response too short for page 1\n");
continue;
}
}
sp->page1_types = (struct type_descriptor_header *)ed;
sp->page1_types_len = sum_headers;
/* ed is on type descr header */
for (i = 0; i < sum_headers; i++, ed += 4) {
if (ed > sp->page1.buf + sp->page1.len) {
log_debug("SES: Response page 1 truncated at %d\n", i);
return 1;
}
}
return 0;
}
static void print_page10(struct ses_pages *sp)
{
unsigned char *ai = sp->page10.buf + 8;
int i = 0, len = 0, eip = 0;
unsigned char *sas = NULL;
while (ai < sp->page10.buf + sp->page10.len) {
printf("%s()[%d]: Inv: %d, EIP: %d, Proto: 0x%04x\n", __func__,
i++, ((ai[0] & 0x80) >> 7), ((ai[0] & 0x10) >> 4),
(unsigned int) (ai[0] & 0xf));
printf("\tDescriptor len (x-1): %d\n", ai[1] + 1);
eip = ai[0] & 0x10;
if (eip)
printf("\tElement Index: %d\n", ai[3]);
len = ai[1] + 2;
if ((ai[0] & 0xf) == SCSI_PROTOCOL_SAS) {
if (eip)
sas = ai + 4;
else
sas = ai + 2;
printf("\tProtocol SAS:\n");
printf("\tNumber of phy descriptors: %d\n", sas[0]);
printf("\tNot all phys: %d, descriptor type: 0x%1x\n",
(sas[1] & 1), ((sas[1] & 0xc0) >> 6));
if (eip) {
printf("\tDevice slot number: %d\n", sas[3]);
sas += 2;
}
sas += 2;
printf("\tDevice type: 0x%01x\n",
(unsigned int)((sas[0] & 0x70) >> 4));
printf("\tSMP Initiator Port: 0x%01x\n",
(unsigned int)((sas[2] & 2) >> 1));
printf("\tSTP Initiator Port: 0x%01x\n",
(unsigned int)((sas[2] & 4) >> 2));
printf("\tSSP Initiator Port: 0x%01x\n",
(unsigned int)((sas[2] & 8) >> 3));
printf("\tSATA DEVICE: 0x%01x\n",
(unsigned int)(sas[3] & 1));
printf("\tSMP Target Port: 0x%01x\n",
(unsigned int)((sas[3] & 2) >> 1));
printf("\tSTP Target Port: 0x%01x\n",
(unsigned int)((sas[3] & 4) >> 2));
printf("\tSSP Target Port: 0x%01x\n",
(unsigned int)((sas[3] & 8) >> 3));
printf("\tSATA Port Selector: 0x%01x\n",
(unsigned int)((sas[3] & 0X80) >> 7));
printf
("\tAttached SAS Address: 0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
sas[4], sas[5], sas[6], sas[7], sas[8], sas[9],
sas[10], sas[11]);
printf
("\tSAS Address: 0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
sas[12], sas[13], sas[14], sas[15], sas[16],
sas[17], sas[18], sas[19]);
printf("\tPHY Identified: 0x%01x\n", sas[20]);
} else
printf("\tProtocol not SAS: 0x%02x, skipping\n",
(unsigned int)(ai[0] & 0xf));
/* */
ai += len;
}
return;
}
int ses_load_pages(int fd, struct ses_pages *sp)
{
int ret;
/* Read configuration. */
ret = get_ses_page(fd, &sp->page1, ENCL_CFG_DIAG_STATUS);
if (ret)
return ret;
ret = process_page1(sp);
if (ret)
return ret;
/* Get Enclosure Status */
ret = get_ses_page(fd, &sp->page2, ENCL_CTRL_DIAG_STATUS);
if (ret)
return ret;
/* Additional Element Status */
ret = get_ses_page(fd, &sp->page10, ENCL_ADDITIONAL_EL_STATUS);
if (ret)
return ret;
if (debug)
print_page10(sp);
return ret;
}
static enum ibpi_pattern ibpi_to_ses(enum ibpi_pattern ibpi)
{
switch (ibpi) {
case IBPI_PATTERN_UNKNOWN:
case IBPI_PATTERN_ONESHOT_NORMAL:
case IBPI_PATTERN_NORMAL:
return SES_REQ_OK;
case IBPI_PATTERN_FAILED_ARRAY:
return SES_REQ_IFA;
case IBPI_PATTERN_DEGRADED:
return SES_REQ_ICA;
case IBPI_PATTERN_REBUILD:
return SES_REQ_REBUILD;
case IBPI_PATTERN_FAILED_DRIVE:
return SES_REQ_FAULT;
case IBPI_PATTERN_LOCATE:
return SES_REQ_IDENT;
case IBPI_PATTERN_HOTSPARE:
return SES_REQ_HOSTSPARE;
case IBPI_PATTERN_PFA:
return SES_REQ_PRDFAIL;
default:
return ibpi;
}
}
static inline void _set_prdfail(unsigned char *u)
{
u[0] |= (1 << 6);
}
static inline void _set_abrt(unsigned char *u)
{
u[1] |= (1 << 0);
}
static inline void _set_rebuild(unsigned char *u)
{
u[1] |= (1 << 1);
}
static inline void _set_ifa(unsigned char *u)
{
u[1] |= (1 << 2);
}
static inline void _set_ica(unsigned char *u)
{
u[1] |= (1 << 3);
}
static inline void _set_cons_check(unsigned char *u)
{
u[1] |= (1 << 4);
}
static inline void _set_hspare(unsigned char *u)
{
u[1] |= (1 << 5);
}
static inline void _set_rsvd_dev(unsigned char *u)
{
u[1] |= (1 << 6);
}
static inline void _set_ok(unsigned char *u)
{
u[1] |= (1 << 7);
}
static inline void _set_ident(unsigned char *u)
{
u[2] |= (1 << 1);
}
static inline void _clr_ident(unsigned char *u)
{
u[2] &= ~(1 << 1);
}
static inline void _set_rm(unsigned char *u)
{
u[2] |= (1 << 2);
}
static inline void _set_ins(unsigned char *u)
{
u[2] |= (1 << 3);
}
static inline void _set_miss(unsigned char *u)
{
u[2] |= (1 << 4);
}
static inline void _set_dnr(unsigned char *u)
{
u[2] |= (1 << 6);
}
static inline void _set_actv(unsigned char *u)
{
u[2] |= (1 << 7);
}
static inline void _set_enbb(unsigned char *u)
{
u[3] |= (1 << 2);
}
static inline void _set_enba(unsigned char *u)
{
u[3] |= (1 << 3);
}
static inline void _set_off(unsigned char *u)
{
u[3] |= (1 << 4);
}
static inline void _set_fault(unsigned char *u)
{
u[3] |= (1 << 5);
}
static int ses_set_message(enum ibpi_pattern ibpi, struct ses_slot_ctrl_elem *el)
{
struct ses_slot_ctrl_elem msg;
memset(&msg, 0, sizeof(msg));
if (ibpi == IBPI_PATTERN_LOCATE_OFF) {
/*
* For locate_off we don't set a new state, just clear the
* IDENT bit and the bits that are reserved or have different
* meanings in Status and Control pages (RQST ACTIVE and
* RQST MISSING).
*/
_clr_ident(el->b);
el->b2 &= 0x4e;
el->b3 &= 0x3c;
return 0;
}
switch (ibpi_to_ses(ibpi)) {
case SES_REQ_ABORT:
_set_abrt(msg.b);
break;
case SES_REQ_REBUILD:
_set_rebuild(msg.b);
break;
case SES_REQ_IFA:
_set_ifa(msg.b);
break;
case SES_REQ_ICA:
_set_ica(msg.b);
break;
case SES_REQ_CONS_CHECK:
_set_cons_check(msg.b);
break;
case SES_REQ_HOSTSPARE:
_set_hspare(msg.b);
break;
case SES_REQ_RSVD_DEV:
_set_rsvd_dev(msg.b);
break;
case SES_REQ_OK:
_set_ok(msg.b);
break;
case SES_REQ_IDENT:
_set_ident(msg.b);
break;
case SES_REQ_RM:
_set_rm(msg.b);
break;
case SES_REQ_INS:
_set_ins(msg.b);
break;
case SES_REQ_MISSING:
_set_miss(msg.b);
break;
case SES_REQ_DNR:
_set_dnr(msg.b);
break;
case SES_REQ_ACTIVE:
_set_actv(msg.b);
break;
case SES_REQ_EN_BB:
_set_enbb(msg.b);
break;
case SES_REQ_EN_BA:
_set_enba(msg.b);
break;
case SES_REQ_DEV_OFF:
_set_off(msg.b);
break;
case SES_REQ_FAULT:
_set_fault(msg.b);
break;
case SES_REQ_PRDFAIL:
_set_prdfail(msg.b);
break;
default:
return 1;
}
*el = msg;
return 0;
}
int ses_write_msg(enum ibpi_pattern ibpi, struct ses_pages *sp, int idx)
{
/* Move do descriptors */
struct ses_slot_ctrl_elem *descriptors = (void *)(sp->page2.buf + 8);
int i;
struct ses_slot_ctrl_elem *desc_element = NULL;
element_type local_element_type = SES_UNSPECIFIED;
for (i = 0; i < sp->page1_types_len; i++) {
const struct type_descriptor_header *t = &sp->page1_types[i];
descriptors++; /* At first, skip overall header. */
if (t->element_type == SES_DEVICE_SLOT ||
t->element_type == SES_ARRAY_DEVICE_SLOT) {
if (local_element_type < t->element_type &&
t->num_of_elements > idx) {
local_element_type = t->element_type;
desc_element = &descriptors[idx];
}
} else {
/*
* Device Slot and Array Device Slot elements are
* always first on the type descriptor header list
*/
break;
}
descriptors += t->num_of_elements;
}
if (desc_element) {
int ret = ses_set_message(ibpi, desc_element);
if (ret)
return ret;
sp->changes++;
/* keep PRDFAIL, clear rest */
desc_element->common_control &= 0x40;
/* set select */
desc_element->common_control |= 0x80;
/* second byte is valid only for Array Device Slot */
if (local_element_type != SES_ARRAY_DEVICE_SLOT)
desc_element->array_slot_control = 0;
return 0;
}
return 1;
}
int ses_send_diag(int fd, struct ses_pages *sp)
{
return sg_ll_send_diag(fd, 0, 1, 0, 0, 0, 0, sp->page2.buf,
sp->page2.len, 0, debug);
}
int ses_get_slots(struct ses_pages *sp, struct ses_slot **out_slots, int *out_slots_count)
{
unsigned char *add_desc = NULL;
unsigned char *ap = NULL, *addr_p = NULL;
int i, j, len = 0;
/* Check Page10 for address. Extract index. */
ap = add_desc = sp->page10.buf + 8;
for (i = 0; i < sp->page1_types_len; i++) {
const struct type_descriptor_header *t = &sp->page1_types[i];
if (t->element_type == SES_DEVICE_SLOT ||
t->element_type == SES_ARRAY_DEVICE_SLOT) {
struct ses_slot *slots;
slots = calloc(t->num_of_elements, sizeof(*slots));
if (!slots)
return -1;
for (j = 0; j < t->num_of_elements; j++, ap += len) {
/* Get Additional Element Status Descriptor */
/* length (x-1) */
len = ap[1] + 2;
if ((ap[0] & 0xf) != SCSI_PROTOCOL_SAS) {
slots[j].index = -1;
continue; /* need SAS PROTO */
}
/* It is a SAS protocol, go on */
if ((ap[0] & 0x10)) /* Check EIP */
addr_p = ap + 8;
else
addr_p = ap + 4;
/* Process only PHY 0 descriptor. */
/* Convert be64 to le64 */
slots[j].sas_addr =
((uint64_t)addr_p[12] << 8*7) |
((uint64_t)addr_p[13] << 8*6) |
((uint64_t)addr_p[14] << 8*5) |
((uint64_t)addr_p[15] << 8*4) |
((uint64_t)addr_p[16] << 8*3) |
((uint64_t)addr_p[17] << 8*2) |
((uint64_t)addr_p[18] << 8*1) |
((uint64_t)addr_p[19]);
slots[j].index = ap[0] & 0x10 ? ap[3] : j;
}
*out_slots = slots;
*out_slots_count = t->num_of_elements;
return 0;
}
}
return 1;
}