/* * Intel(R) Enclosure LED Utilities * Copyright (C) 2009-2019 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 #include #include #include #include #include #include #include #include #include #if _HAVE_DMALLOC_H #include #endif #include #include #include "cntrl.h" #include "config.h" #include "enclosure.h" #include "list.h" #include "scsi.h" #include "ses.h" #include "status.h" #include "sysfs.h" #include "utils.h" static int debug = 0; 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 struct ses_pages *ses_init(void) { struct ses_pages *sp; sp = calloc(1, sizeof(*sp)); if (!sp) return NULL; sp->page1 = calloc(1, sizeof(struct ses_page)); if (!sp->page1) goto sp1; sp->page2 = calloc(1, sizeof(struct ses_page)); if (!sp->page2) goto sp2; return sp; sp2: free(sp->page1); sp1: free(sp); return NULL; } static void ses_free(struct ses_pages *sp) { if (!sp) return; free(sp->page1); free(sp->page2); free(sp->page10); free(sp); } static void dump_p10(unsigned char *p) { int i; printf("----------------------------------------------\n"); for (i = 0; i < 8; i++, p += 16) { printf("%p: %02x %02x %02x %02x %02x %02x %02x " \ "%02x %02x %02x %02x %02x %02x %02x %02x %02x\n", (void *)p, p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); } } static int enclosure_open(const struct enclosure_device *enclosure) { int fd = -1; if (enclosure->dev_path) fd = open(enclosure->dev_path, O_RDWR); return fd; } static int enclosure_load_pages(struct enclosure_device *enclosure) { int ret; int fd; struct ses_pages *sp; if (enclosure->ses_pages) return 0; fd = enclosure_open(enclosure); if (fd == -1) return 1; sp = ses_init(); if (!sp) { ret = 1; goto end; } /* Read configuration. */ ret = get_ses_page(fd, sp->page1, ENCL_CFG_DIAG_STATUS); if (ret) goto end; ret = process_page1(sp); if (ret) goto end; /* Get Enclosure Status */ ret = get_ses_page(fd, sp->page2, ENCL_CTRL_DIAG_STATUS); end: close(fd); if (ret) ses_free(sp); else enclosure->ses_pages = sp; return ret; } static int enclosure_load_page10(struct enclosure_device *enclosure) { int ret; int fd; struct ses_page *p; if (enclosure->ses_pages && enclosure->ses_pages->page10) return 0; ret = enclosure_load_pages(enclosure); if (ret) return ret; fd = enclosure_open(enclosure); if (fd == -1) return 1; p = calloc(1, sizeof(struct ses_page)); if (!p) { ret = 1; goto end; } /* Additional Element Status */ ret = get_ses_page(fd, p, ENCL_ADDITIONAL_EL_STATUS); end: close(fd); if (ret) free(p); else enclosure->ses_pages->page10 = p; return ret; } static void enclosure_free_pages(struct enclosure_device *enclosure) { ses_free(enclosure->ses_pages); enclosure->ses_pages = NULL; } 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; } 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 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; } static int ses_write_msg(enum ibpi_pattern ibpi, struct block_device *device) { struct ses_pages *sp = device->enclosure->ses_pages; int idx = device->encl_index; /* 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++) { 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; /* 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; } static int ses_send_diag(struct enclosure_device *enclosure) { int ret; int fd; fd = enclosure_open(enclosure); if (fd == -1) return 1; ret = sg_ll_send_diag(fd, 0, 1, 0, 0, 0, 0, enclosure->ses_pages->page2->buf, enclosure->ses_pages->page2->len, 0, debug); close(fd); return ret; } static char *get_drive_end_dev(const char *path) { char *s, *c, *p; c = strstr(path, "end_device"); if (!c) return NULL; s = strchr(c, '/'); if (!s) return NULL; p = calloc(s - c + 1, sizeof(*p)); if (!p) return NULL; strncpy(p, c, s - c); return p; } static uint64_t get_drive_sas_addr(const char *path) { uint64_t ret = 0; size_t size = strlen(path) * 2; char *buff, *end_dev; /* Make big buffer. */ buff = malloc(size + 1); if (!buff) return ret; end_dev = get_drive_end_dev(path); if (!end_dev) { free(buff); return ret; } snprintf(buff, size, "/sys/class/sas_end_device/%s/device/sas_device/%s", end_dev, end_dev); ret = get_uint64(buff, ret, "sas_address"); free(end_dev); free(buff); return ret; } static int get_encl_slot(struct block_device *device) { struct ses_pages *sp; unsigned char *add_desc = NULL; unsigned char *ap = NULL, *addr_p = NULL; int i, j, len = 0; uint64_t addr, addr_cmp; int idx; /* try to get slot from sysfs */ idx = get_int(device->cntrl_path, -1, "slot"); if (idx != -1) return idx; /* * Older kernels may not have the "slot" sysfs attribute, * fallback to Page10 method. */ if (enclosure_load_page10(device->enclosure)) return -1; sp = device->enclosure->ses_pages; addr = get_drive_sas_addr(device->sysfs_path); if (!addr) return -1; if (debug) print_page10(sp); /* Check Page10 for address. Extract index. */ ap = add_desc = sp->page10->buf + 8; for (i = 0; i < sp->page1_types_len; i++) { struct type_descriptor_header *t = &sp->page1_types[i]; if (t->element_type == SES_DEVICE_SLOT || t->element_type == SES_ARRAY_DEVICE_SLOT) { for (j = 0; j < t->num_of_elements; j++, ap += len) { if (debug) dump_p10(ap); /* Get Additional Element Status Descriptor */ /* length (x-1) */ len = ap[1] + 2; if ((ap[0] & 0xf) != SCSI_PROTOCOL_SAS) 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 */ addr_cmp = ((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]); if (addr == addr_cmp) { idx = ap[0] & 0x10 ? ap[3] : j; return idx; } } } else { /* * Device Slot and Array Device Slot elements are * always first on the type descriptor header list */ break; } } return -1; } /** */ static int _slot_match(const char *slot_path, const char *device_path) { char temp[PATH_MAX], link[PATH_MAX]; snprintf(temp, sizeof(temp), "%s/device", slot_path); if (realpath(temp, link) == NULL) return 0; return strncmp(link, device_path, strlen(link)) == 0; } /** */ static char *_slot_find(const char *enclo_path, const char *device_path) { struct list dir; char *temp, *result = NULL; if (scan_dir(enclo_path, &dir) == 0) { list_for_each(&dir, temp) { if (_slot_match(temp, device_path)) { result = str_dup(temp); break; } } list_erase(&dir); } return result; } int scsi_get_enclosure(struct block_device *device) { struct enclosure_device *encl; if (!device || !device->sysfs_path) return 0; list_for_each(sysfs_get_enclosure_devices(), encl) { if (_slot_match(encl->sysfs_path, device->cntrl_path)) { device->enclosure = encl; device->encl_index = get_encl_slot(device); break; } } return (device->enclosure != NULL && device->encl_index != -1); } /** */ int scsi_ses_write(struct block_device *device, enum ibpi_pattern ibpi) { int ret; if (!device || !device->sysfs_path || !device->enclosure || device->encl_index == -1) __set_errno_and_return(EINVAL); /* write only if state has changed */ if (ibpi == device->ibpi_prev) return 1; if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > SES_REQ_FAULT)) __set_errno_and_return(ERANGE); ret = enclosure_load_pages(device->enclosure); if (ret) { log_warning ("Unable to send %s message to %s. Device is missing?", ibpi2str(ibpi), strstr(device->sysfs_path, "host")); return ret; } return ses_write_msg(ibpi, device); } int scsi_ses_flush(struct block_device *device) { int ret; if (!device || !device->enclosure) return 1; if (!device->enclosure->ses_pages) return 0; ret = ses_send_diag(device->enclosure); enclosure_free_pages(device->enclosure); return ret; } /** * @brief Gets a path to slot of sas controller. * * This function returns a sysfs path to component of enclosure the device * belongs to. * * @param[in] path Canonical sysfs path to block device. * * @return A sysfs path to controller device associated with the given * block device if successful, otherwise NULL pointer. */ static char *sas_get_slot_path(const char *path, const char *ctrl_path) { char *host; char host_path[PATH_MAX] = { 0 }; size_t ctrl_path_len = strlen(ctrl_path); if (strncmp(path, ctrl_path, ctrl_path_len) != 0) return NULL; host = get_path_hostN(path); if (host) { snprintf(host_path, sizeof(host_path), "%s/%s/bsg/sas_%s", ctrl_path, host, host); free(host); } return str_dup(host_path); } /** */ static char *_get_enc_slot_path(const char *path) { struct enclosure_device *device; char *result = NULL; list_for_each(sysfs_get_enclosure_devices(), device) { result = _slot_find(device->sysfs_path, path); if (result != NULL) break; } return result; } /** */ char *scsi_get_slot_path(const char *path, const char *ctrl_path) { char *result = NULL; result = _get_enc_slot_path(path); if (!result) result = sas_get_slot_path(path, ctrl_path); return result; }