/* * Intel(R) Enclosure LED Utilities * Copyright (C) 2011-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 #include #include #include #include #if _HAVE_DMALLOC_H #include #endif #include "block.h" #include "cntrl.h" #include "config.h" #include "enclosure.h" #include "ibpi.h" #include "list.h" #include "scsi.h" #include "smp.h" #include "status.h" #include "sysfs.h" #include "utils.h" #define GPIO_TX_GP1 0x01 #define INIT_IBPI(act, loc, err) \ { .error = err, \ .locate = loc, \ .activity = act \ } #define LED_OFF 0 #define LED_ON 1 #define LED_4HZ 2 #define LED_I4HZ 3 #define LED_EOF 4 #define LED_SOF 5 #define LED_2HZ 6 #define LED_I2HZ 7 static const struct gpio_rx_table { struct gpio_tx_register_byte pattern; int support_mask; } ibpi2sgpio[] = { [IBPI_PATTERN_UNKNOWN] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 1 }, /* OK */ [IBPI_PATTERN_ONESHOT_NORMAL] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 1 }, /* OK */ [IBPI_PATTERN_NORMAL] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 1 }, /* OK */ [IBPI_PATTERN_DEGRADED] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 0 }, /* NO */ [IBPI_PATTERN_REBUILD] = { INIT_IBPI(LED_SOF,LED_ON,LED_ON), 1 }, /* OK */ [IBPI_PATTERN_FAILED_ARRAY] = { INIT_IBPI(LED_SOF,LED_4HZ,LED_OFF), 0 }, /* NO */ [IBPI_PATTERN_HOTSPARE] = { INIT_IBPI(LED_SOF,LED_OFF,LED_4HZ), 0 }, /* NO */ [IBPI_PATTERN_PFA] = { INIT_IBPI(LED_SOF,LED_OFF,LED_2HZ), 0 }, /* NO */ [IBPI_PATTERN_FAILED_DRIVE] = { INIT_IBPI(LED_SOF,LED_OFF,LED_ON), 1 }, /* OK */ [IBPI_PATTERN_LOCATE] = { INIT_IBPI(LED_SOF,LED_ON,LED_OFF), 1 }, /* OK */ [IBPI_PATTERN_LOCATE_OFF] = { INIT_IBPI(LED_SOF,LED_OFF,LED_OFF), 1 } /* OK */ }; struct smp_read_response_frame_header { uint8_t frame_type; /* =0x41 */ uint8_t function; /* =0x02 for read, 0x82 for write */ uint8_t function_result; uint8_t reserved; uint32_t read_data[]; /* variable length of data */ /* uint32_t crc; */ } __attribute__ ((__packed__)); struct smp_write_response_frame { uint8_t frame_type; /* =0x41 */ uint8_t function; /* =0x02 for read, 0x82 for write */ uint8_t function_result; uint8_t reserved; uint32_t crc; } __attribute__ ((__packed__)); struct smp_read_request_frame { uint8_t frame_type; /* =0x40 */ uint8_t function; /* =0x02 for read, 0x82 for write */ uint8_t register_type; uint8_t register_index; uint8_t register_count; uint8_t reserved[3]; uint32_t crc; } __attribute__ ((__packed__)); struct smp_write_request_frame_header { uint8_t frame_type; /* =0x40 */ uint8_t function; /* =0x02 for read, 0x82 for write */ uint8_t register_type; uint8_t register_index; uint8_t register_count; uint8_t reserved[3]; uint32_t data[]; /* variable length of data */ /* uint32_t crc; */ } __attribute__ ((__packed__)); /** * to_sas_gpio_gp_bit - given the gpio frame data find the byte/bit position of 'od' * @od: od bit to find * @data: incoming bitstream (from frame) * @index: requested data register index (from frame) * @count: total number of registers in the bitstream (from frame) * @bit: bit position of 'od' in the returned byte * * returns NULL if 'od' is not in 'data' * * From SFF-8485 v0.7: * "In GPIO_TX[1], bit 0 of byte 3 contains the first bit (i.e., OD0.0) * and bit 7 of byte 0 contains the 32nd bit (i.e., OD10.1). * * In GPIO_TX[2], bit 0 of byte 3 contains the 33rd bit (i.e., OD10.2) * and bit 7 of byte 0 contains the 64th bit (i.e., OD21.0)." * * The general-purpose (raw-bitstream) RX registers have the same layout * although 'od' is renamed 'id' for 'input data'. * * SFF-8489 defines the behavior of the LEDs in response to the 'od' values. */ static unsigned char *to_sas_gpio_gp_bit(unsigned int od, unsigned char *data, unsigned char index, unsigned char count, unsigned char *bit) { unsigned int reg; unsigned char byte; /* gp registers start at index 1 */ if (index == 0) return NULL; index--; /* make index 0-based */ if (od < index * 32) return NULL; od -= index * 32; reg = od >> 5; if (reg >= count) return NULL; od &= (1 << 5) - 1; byte = 3 - (od >> 3); *bit = od & ((1 << 3) - 1); return &data[reg * 4 + byte]; } int try_test_sas_gpio_gp_bit(unsigned int od, unsigned char *data, unsigned char index, unsigned char count) { unsigned char *byte; unsigned char bit; byte = to_sas_gpio_gp_bit(od, data, index, count, &bit); if (!byte) return -1; return (*byte >> bit) & 1; } int try_set_sas_gpio_gp_bit(unsigned int od, unsigned char *data, unsigned char index, unsigned char count) { unsigned char *byte; unsigned char bit; byte = to_sas_gpio_gp_bit(od, data, index, count, &bit); if (!byte) return 0; *byte |= 1 << bit; return 1; } int try_clear_sas_gpio_gp_bit(unsigned int od, unsigned char *data, unsigned char index, unsigned char count) { unsigned char *byte; unsigned char bit; byte = to_sas_gpio_gp_bit(od, data, index, count, &bit); if (!byte) return 0; *byte &= ~(1 << bit); return 1; } /** * set_raw_pattern - turn a tx register into a tx_gp bitstream * * takes @dev_idx (phy index) and a @pattern (error, locate, activity) * tuple and modifies the bitstream in @data accordingly */ int set_raw_pattern(unsigned int dev_idx, unsigned char *data, const struct gpio_tx_register_byte *pattern) { int od_offset = dev_idx * 3; int rc = 0; if (pattern->activity == LED_ON) rc += try_set_sas_gpio_gp_bit(od_offset + 0, data, GPIO_TX_GP1, 1); else rc += try_clear_sas_gpio_gp_bit(od_offset + 0, data, GPIO_TX_GP1, 1); if (pattern->locate == LED_ON) rc += try_set_sas_gpio_gp_bit(od_offset + 1, data, GPIO_TX_GP1, 1); else rc += try_clear_sas_gpio_gp_bit(od_offset + 1, data, GPIO_TX_GP1, 1); if (pattern->error == LED_ON) rc += try_set_sas_gpio_gp_bit(od_offset + 2, data, GPIO_TX_GP1, 1); else rc += try_clear_sas_gpio_gp_bit(od_offset + 2, data, GPIO_TX_GP1, 1); return rc == 3; } /** * @brief open device for smp protocol */ static int _open_smp_device(const char *filename) { char buf[PATH_MAX]; FILE *df; int hba_fd; int dmaj, dmin; snprintf(buf, sizeof(buf), "%s/dev", filename); df = fopen(buf, "r"); if (!df) return -1; if (fgets(buf, sizeof(buf), df) == NULL) { fclose(df); return -1; } if (sscanf(buf, "%d:%d", &dmaj, &dmin) != 2) { fclose(df); return -1; } fclose(df); snprintf(buf, sizeof(buf), "/var/tmp/led.%d.%d.%d", dmaj, dmin, getpid()); if (mknod(buf, S_IFCHR | S_IRUSR | S_IWUSR, makedev(dmaj, dmin)) < 0) return -1; hba_fd = open(buf, O_RDWR); unlink(buf); if (hba_fd < 0) return -1; return hba_fd; } /** * @brief close smp device */ static int _close_smp_device(int fd) { return close(fd); } /** @brief use sg protocol in order to send data directly to hba driver */ static int _send_smp_frame(int hba, void *data, size_t data_size, void *response, size_t *response_size) { struct sg_io_v4 sg_frame; uint8_t request_buf[SCSI_MAX_CDB_LENGTH]; int response_status = 0; /* wrap the frame into sg structure */ memset(&sg_frame, 0, sizeof(sg_frame)); sg_frame.guard = 'Q'; sg_frame.protocol = BSG_PROTOCOL_SCSI; sg_frame.subprotocol = BSG_SUB_PROTOCOL_SCSI_TRANSPORT; sg_frame.request_len = sizeof(request_buf); sg_frame.request = (uintptr_t) request_buf; sg_frame.dout_xfer_len = data_size; sg_frame.dout_xferp = (uintptr_t) data; sg_frame.din_xfer_len = *response_size; sg_frame.din_xferp = (uintptr_t) response; sg_frame.timeout = SG_RESPONSE_TIMEOUT; /* send ioctl */ if (ioctl(hba, SG_IO, &sg_frame) < 0) return -1; /* return status */ if (sg_frame.driver_status) response_status = sg_frame.driver_status; else if (sg_frame.transport_status) response_status = sg_frame.transport_status; else if (sg_frame.device_status) response_status = sg_frame.device_status; *response_size = sg_frame.din_xfer_len - sg_frame.din_resid; return response_status; } /* 1024 bytes for data, 4 for crc */ #define MAX_SMP_FRAME_DATA 1024 #define MAX_SMP_FRAME_LEN (sizeof(struct smp_write_request_frame_header) + \ MAX_SMP_FRAME_DATA + SMP_FRAME_CRC_LEN) /** @brief prepare full smp frame ready to send to hba @note len is a number of 32bit words */ static int _start_smp_write_gpio(int hba, struct smp_write_request_frame_header *header, void *data, size_t len) { uint8_t buf[MAX_SMP_FRAME_LEN]; struct smp_write_response_frame response; size_t response_size = sizeof(response); int status; memset(&response, 0, sizeof(response)); /* create full frame */ if (len * SMP_DATA_CHUNK_SIZE > MAX_SMP_FRAME_DATA) __set_errno_and_return(EINVAL); memset(buf, 0, sizeof(buf)); memcpy(buf, header, sizeof(*header)); memcpy(buf + sizeof(*header), data, len * SMP_DATA_CHUNK_SIZE); status = _send_smp_frame(hba, buf, sizeof(*header) + len * SMP_DATA_CHUNK_SIZE + SMP_FRAME_CRC_LEN, &response, &response_size); /* if frame is somehow malformed return failure */ if (status != GPIO_STATUS_OK || response.frame_type != SMP_FRAME_TYPE_RESP || response.function != header->function) { return GPIO_STATUS_FAILURE; } return response.function_result; } /** @brief prepare smp frame header */ int smp_write_gpio(const char *path, int smp_reg_type, int smp_reg_index, int smp_reg_count, void *data, size_t len) { struct smp_write_request_frame_header header; int status; header.frame_type = SMP_FRAME_TYPE_REQ; header.function = SMP_FUNC_GPIO_WRITE; header.register_type = smp_reg_type; header.register_index = smp_reg_index; header.register_count = smp_reg_count; memset(header.reserved, 0, sizeof(header.reserved)); int fd = _open_smp_device(path); status = _start_smp_write_gpio(fd, &header, data, len); _close_smp_device(fd); return status; } #define BLINK_GEN_1HZ 8 #define BLINK_GEN_2HZ 4 #define BLINK_GEN_4HZ 2 #define DEFAULT_FORCED_ACTIVITY_OFF 1 #define DEFAULT_MAXIMUM_ACTIVITY_ON 2 #define DEFAULT_STRETCH_ACTIVITY_OFF 0 #define DEFAULT_STRETCH_ACTIVITY_ON 0 /* one data chunk is 32bit long */ #define SMP_DATA_CHUNKS 1 struct gpio_tx_register_byte *get_bdev_ibpi_buffer(struct block_device *bdevice) { if (bdevice && bdevice->host) return bdevice->host->ibpi_state_buffer; return NULL; } /** */ int scsi_smp_fill_buffer(struct block_device *device, enum ibpi_pattern ibpi) { const char *sysfs_path = device->cntrl_path; struct gpio_tx_register_byte *gpio_tx; if (sysfs_path == NULL) __set_errno_and_return(EINVAL); if ((ibpi < IBPI_PATTERN_NORMAL) || (ibpi > IBPI_PATTERN_LOCATE_OFF)) __set_errno_and_return(ERANGE); if (!device->cntrl) { log_debug("No ctrl dev for '%s'", strstr(sysfs_path, "host")); __set_errno_and_return(ENODEV); } if (device->cntrl->cntrl_type != CNTRL_TYPE_SCSI) { log_debug("No SCSI ctrl dev '%s'", strstr(sysfs_path, "host")); __set_errno_and_return(EINVAL); } if (!device->host) { log_debug("No host for '%s'", strstr(sysfs_path, "host")); __set_errno_and_return(ENODEV); } if (device->cntrl->isci_present && !ibpi2sgpio[ibpi].support_mask) { char *c = strrchr(device->sysfs_path, '/'); if (c++) { log_debug ("pattern %s not supported for device (/dev/%s)", ibpi2str(ibpi), c); fprintf(stderr, "%s(): pattern %s not supported for device (/dev/%s)\n", __func__, ibpi2str(ibpi), c); } else { log_debug("pattern %s not supported for device %s", ibpi2str(ibpi), device->sysfs_path); fprintf(stderr, "%s(): pattern %s not supported for device\n\t(%s)\n", __func__, ibpi2str(ibpi), device->sysfs_path); } __set_errno_and_return(ENOTSUP); } gpio_tx = get_bdev_ibpi_buffer(device); if (!gpio_tx) { log_debug("%s(): no IBPI buffer. Skipping.", __func__); __set_errno_and_return(ENODEV); } if (device->cntrl->isci_present) { /* update bit stream for this device */ set_raw_pattern(device->phy_index, &device->host->bitstream[0], &ibpi2sgpio[ibpi].pattern); } else { /* * GPIO_TX[n] register has the highest numbered drive of the * four in the first byte and the lowest numbered drive in the * fourth byte. See SFF-8485 Rev. 0.7 Table 24. */ gpio_tx[device->phy_index + 3 - (device->phy_index % 4) * 2] = ibpi2sgpio[ibpi].pattern; } /* write only if state has changed */ if (ibpi != device->ibpi_prev) device->host->flush = 1; return 1; } int scsi_smp_write_buffer(struct block_device *device) { const char *sysfs_path = device->cntrl_path; if (sysfs_path == NULL) __set_errno_and_return(EINVAL); if (!device->host) __set_errno_and_return(ENODEV); if (device->host->flush) { device->host->flush = 0; /* re-transmit the bitstream */ if (device->cntrl->isci_present) { return smp_write_gpio(sysfs_path, GPIO_REG_TYPE_TX_GP, GPIO_TX_GP1, 1, &device->host->bitstream[0], SMP_DATA_CHUNKS); } else { return smp_write_gpio(sysfs_path, GPIO_REG_TYPE_TX, 0, (device->host->ports+3)/4, device->host->ibpi_state_buffer, (device->host->ports+3)/4); } } else return 1; } /** */ static void init_smp(struct cntrl_device *device) { struct _host_type *hosts; int i; if (!device) return; for (hosts = device->hosts; hosts; hosts = hosts->next) { /* already initialized */ if (hosts->ibpi_state_buffer) continue; hosts->ibpi_state_buffer = calloc(hosts->ports, sizeof(struct gpio_tx_register_byte)); if (!hosts->ibpi_state_buffer) continue; for (i = 0; i < hosts->ports; i++) set_raw_pattern(i, &hosts->bitstream[0], &ibpi2sgpio [IBPI_PATTERN_ONESHOT_NORMAL].pattern); hosts->flush = 0; } } /** */ int cntrl_init_smp(const char *path, struct cntrl_device *cntrl) { char *path2 = NULL; char *c; int host, port = 0; struct dirent *de; DIR *d; if (!cntrl) return port; /* Other case - just init controller. */ if (path && strstr(path, "port-")) { path2 = str_dup(path); if (!path2) return port; c = strstr(path2, "port-"); if (!c) { /* Should not happen. */ log_debug("%s() missing 'port' in path '%s'", __func__, path2); free(path2); return port; } c = strchr(c, '/'); if (!c) { free(path2); return port; } *c = 0; /* And now path2 has only up to 'port-...' string. */ /* this should open port-XX:X directory * FIXME: for enclosure it may be port-XX:Y:Z but it's a second * occurrence * * We may try this on the missing device. * */ d = opendir(path2); if (!d) { log_debug("%s() Error dir open '%s', path ='%s'", __func__, path2, path); free(path2); return port; } while ((de = readdir(d))) { if ((strcmp(de->d_name, ".") == 0) || (strcmp(de->d_name, "..")) == 0) { continue; } if (strncmp(de->d_name, "phy-", strlen("phy-")) == 0) { /* Need link called "phy-XX:Y * Y is real phy we need. * This can also be found * in phy_identifier file */ if (sscanf(de->d_name, "phy-%d:%d", &host, &port) != 2) continue; break; } } closedir(d); free(path2); } init_smp(cntrl); return port; }