| /*- |
| * Copyright (c) 2008, 2009 Yahoo!, Inc. |
| * Copyright 2015-2024 Rivoreo |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. The names of the authors may not be used to endorse or promote |
| * products derived from this software without specific prior written |
| * permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * |
| * $FreeBSD: releng/11.1/usr.sbin/mfiutil/mfi_cmd.c 237259 2012-06-19 06:18:37Z eadler $ |
| */ |
| |
| #include <sys/param.h> |
| #if defined __FreeBSD__ && !defined __FreeBSD_kernel__ |
| #define __FreeBSD_kernel__ |
| #endif |
| #include <sys/ioctl.h> |
| #ifdef __SVR4 |
| #include <sys/ioccom.h> |
| #endif |
| #if defined __FreeBSD_kernel__ || defined __DragonFly__ |
| #include <sys/sysctl.h> |
| #endif |
| #include <sys/uio.h> |
| #include <stddef.h> |
| #include <err.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include "mfiutil.h" |
| #include <dev/mfi/mfi_ioctl.h> |
| #if defined __sun && defined __SVR4 && defined HAVE_LIBDEVINFO |
| #include <libdevinfo.h> |
| #endif |
| |
| static const char *mfi_status_codes[] = { |
| "Command completed successfully", |
| "Invalid command", |
| "Invalid DMCD opcode", |
| "Invalid parameter", |
| "Invalid Sequence Number", |
| "Abort isn't possible for the requested command", |
| "Application 'host' code not found", |
| "Application in use", |
| "Application not initialized", |
| "Array index invalid", |
| "Array row not empty", |
| "Configuration resource conflict", |
| "Device not found", |
| "Drive too small", |
| "Flash memory allocation failed", |
| "Flash download already in progress", |
| "Flash operation failed", |
| "Bad flash image", |
| "Incomplete flash image", |
| "Flash not open", |
| "Flash not started", |
| "Flush failed", |
| "Specified application doesn't have host-resident code", |
| "Volume consistency check in progress", |
| "Volume initialization in progress", |
| "Volume LBA out of range", |
| "Maximum number of volumes are already configured", |
| "Volume is not OPTIMAL", |
| "Volume rebuild in progress", |
| "Volume reconstruction in progress", |
| "Volume RAID level is wrong for requested operation", |
| "Too many spares assigned", |
| "Scratch memory not available", |
| "Error writing MFC data to SEEPROM", |
| "Required hardware is missing", |
| "Item not found", |
| "Volume drives are not within an enclosure", |
| "Drive clear in progress", |
| "Drive type mismatch (SATA vs SAS)", |
| "Patrol read disabled", |
| "Invalid row index", |
| "SAS Config - Invalid action", |
| "SAS Config - Invalid data", |
| "SAS Config - Invalid page", |
| "SAS Config - Invalid type", |
| "SCSI command completed with error", |
| "SCSI I/O request failed", |
| "SCSI RESERVATION_CONFLICT", |
| "One or more flush operations during shutdown failed", |
| "Firmware time is not set", |
| "Wrong firmware or drive state", |
| "Volume is offline", |
| "Peer controller rejected request", |
| "Unable to inform peer of communication changes", |
| "Volume reservation already in progress", |
| "I2C errors were detected", |
| "PCI errors occurred during XOR/DMA operation", |
| "Diagnostics failed", |
| "Unable to process command as boot messages are pending", |
| "Foreign configuration is incomplete" |
| }; |
| |
| const char * |
| mfi_status(uint8_t status_code) |
| { |
| static char buffer[16]; |
| |
| if (status_code == MFI_STAT_INVALID_STATUS) |
| return ("Invalid status"); |
| if (status_code < sizeof(mfi_status_codes) / sizeof(char *)) |
| return (mfi_status_codes[status_code]); |
| snprintf(buffer, sizeof(buffer), "Status: 0x%02x", status_code); |
| return (buffer); |
| } |
| |
| const char * |
| mfi_raid_level(uint8_t primary_level, uint8_t secondary_level) |
| { |
| static char buf[16]; |
| |
| switch (primary_level) { |
| case DDF_RAID0: |
| return ("RAID-0"); |
| case DDF_RAID1: |
| if (secondary_level != 0) |
| return ("RAID-10"); |
| else |
| return ("RAID-1"); |
| case DDF_RAID1E: |
| return ("RAID-1E"); |
| case DDF_RAID3: |
| return ("RAID-3"); |
| case DDF_RAID5: |
| if (secondary_level != 0) |
| return ("RAID-50"); |
| else |
| return ("RAID-5"); |
| case DDF_RAID5E: |
| return ("RAID-5E"); |
| case DDF_RAID5EE: |
| return ("RAID-5EE"); |
| case DDF_RAID6: |
| if (secondary_level != 0) |
| return ("RAID-60"); |
| else |
| return ("RAID-6"); |
| case DDF_JBOD: |
| return ("JBOD"); |
| case DDF_CONCAT: |
| return ("CONCAT"); |
| default: |
| sprintf(buf, "LVL 0x%02x", primary_level); |
| return (buf); |
| } |
| } |
| |
| static int |
| mfi_query_disk(int fd, uint8_t target_id, struct mfi_query_disk *info) |
| { |
| #ifdef HAVE_MFI_DRIVER |
| if(use_mfi) { |
| memset(info, 0, sizeof(*info)); |
| info->array_id = target_id; |
| if (ioctl(fd, MFIIO_QUERY_DISK, info) < 0) return (-1); |
| if (!info->present) { |
| errno = ENXIO; |
| return (-1); |
| } |
| return (0); |
| } |
| #endif |
| return -1; |
| } |
| |
| const char * |
| mfi_volume_name(int fd, uint8_t target_id) |
| { |
| static struct mfi_query_disk info; |
| static char buf[4]; |
| |
| if (mfi_query_disk(fd, target_id, &info) < 0) { |
| snprintf(buf, sizeof(buf), "%u", target_id); |
| return (buf); |
| } |
| return (info.devname); |
| } |
| |
| int |
| mfi_volume_busy(int fd, uint8_t target_id) |
| { |
| struct mfi_query_disk info; |
| |
| /* Assume it isn't mounted if we can't get information. */ |
| if (mfi_query_disk(fd, target_id, &info) < 0) |
| return (0); |
| return (info.open != 0); |
| } |
| |
| /* |
| * Check if the running kernel supports changing the RAID |
| * configuration of the mfi controller. |
| */ |
| int |
| mfi_reconfig_supported(void) |
| { |
| #if defined __FreeBSD_kernel__ || defined __DragonFly__ |
| char mibname[64]; |
| size_t len; |
| int dummy; |
| |
| len = sizeof(dummy); |
| snprintf(mibname, sizeof(mibname), "dev.mfi.%d.delete_busy_volumes", |
| mfi_unit); |
| return (sysctlbyname(mibname, &dummy, &len, NULL, 0) == 0); |
| #else |
| return 1; |
| #endif |
| } |
| |
| int |
| mfi_lookup_volume(int fd, const char *name, uint8_t *target_id) |
| { |
| char *cp; |
| long val; |
| |
| /* If it's a valid number, treat it as a raw target ID. */ |
| val = strtol(name, &cp, 0); |
| if (*cp == '\0') { |
| *target_id = val; |
| return (0); |
| } |
| |
| #ifdef HAVE_MFI_DRIVER |
| if(use_mfi) { |
| struct mfi_query_disk info; |
| struct mfi_ld_list list; |
| unsigned int i; |
| if (mfi_dcmd_command(fd, MFI_DCMD_LD_GET_LIST, &list, sizeof(list), |
| NULL, 0, NULL) < 0) { |
| return (-1); |
| } |
| CONVLE32(&list.ld_count); |
| for (i = 0; i < list.ld_count; i++) { |
| if (mfi_query_disk(fd, list.ld_list[i].ld.v.target_id, |
| &info) < 0) { |
| continue; |
| } |
| if (strcmp(name, info.devname) == 0) { |
| *target_id = list.ld_list[i].ld.v.target_id; |
| return (0); |
| } |
| } |
| } |
| #endif |
| errno = EINVAL; |
| return (-1); |
| } |
| |
| int |
| mfi_dcmd_command(int fd, union mfi_dcmd_opcode opcode, void *buf, size_t bufsize, |
| const uint8_t *mbox, size_t mboxlen, uint8_t *statusp) |
| { |
| #if defined __sun && defined __SVR4 |
| struct mfi_solaris_ioc_packet ioc; |
| #else |
| #ifdef HAVE_MFI_DRIVER |
| struct mfi_ioc_passthru mfi_ioc; |
| #endif |
| struct mfi_ioc_packet mrsas_ioc; |
| #endif |
| struct mfi_dcmd_frame *dcmd; |
| int r; |
| |
| if ((mbox != NULL && (mboxlen == 0 || mboxlen > MFI_MBOX_SIZE)) || |
| (mbox == NULL && mboxlen != 0)) { |
| errno = EINVAL; |
| return (-1); |
| } |
| |
| #if defined __sun && defined __SVR4 |
| memset(&ioc, 0, sizeof ioc); |
| dcmd = (struct mfi_dcmd_frame *)ioc.frame; |
| #else |
| #ifdef HAVE_MFI_DRIVER |
| if(use_mfi) { |
| memset(&mfi_ioc, 0, sizeof mfi_ioc); |
| dcmd = &mfi_ioc.ioc_frame; |
| } else |
| #endif |
| { |
| memset(&mrsas_ioc, 0, sizeof mrsas_ioc); |
| mrsas_ioc.mfi_adapter_no = mfi_unit; |
| dcmd = &mrsas_ioc.mfi_frame.dcmd; |
| } |
| #endif /* __sun && __SVR4 */ |
| |
| if (mbox) memcpy(dcmd->mbox, mbox, mboxlen); |
| dcmd->header.cmd = MFI_CMD_DCMD; |
| dcmd->header.timeout = 0; |
| dcmd->header.flags = bufsize ? (MFI_FRAME_DATAOUT | MFI_FRAME_DATAIN) : 0; |
| dcmd->header.data_len = bufsize; |
| dcmd->opcode = opcode.i; |
| |
| #if defined __sun && defined __SVR4 |
| dcmd->header.sg_count = 1; |
| dcmd->sgl.sg[0].addr = buf; |
| dcmd->sgl.sg[0].len = (uint32_t)bufsize; |
| r = ioctl(fd, MFI_SOLARIS_IOCTL_FIRMWARE, &ioc); |
| #else |
| #ifdef HAVE_MFI_DRIVER |
| if(use_mfi) { |
| mfi_ioc.buf = buf; |
| mfi_ioc.buf_size = bufsize; |
| r = ioctl(fd, MFIIO_PASSTHRU, &mfi_ioc); |
| } else |
| #endif |
| { |
| dcmd->header.sg_count = 1; |
| dcmd->sgl.sg32[0].addr = (uintptr_t)buf; |
| dcmd->sgl.sg32[0].len = (uint32_t)bufsize; |
| mrsas_ioc.mfi_sge_count = 1; |
| mrsas_ioc.mfi_sgl_off = offsetof(struct mfi_dcmd_frame, sgl); |
| mrsas_ioc.mfi_sgl[0].iov_base = buf; |
| mrsas_ioc.mfi_sgl[0].iov_len = bufsize; |
| r = ioctl(fd, MFI_CMD, &mrsas_ioc); |
| } |
| #endif /* __sun && __SVR4 */ |
| if (r < 0) |
| return (r); |
| |
| if (statusp != NULL) |
| *statusp = dcmd->header.cmd_status; |
| else if (dcmd->header.cmd_status != MFI_STAT_OK) { |
| warnx("Command failed: %s", |
| mfi_status(dcmd->header.cmd_status)); |
| errno = EIO; |
| return (-1); |
| } |
| return (0); |
| } |
| |
| int |
| mfi_ctrl_get_info(int fd, struct mfi_ctrl_info *info, uint8_t *statusp) |
| { |
| size_t i; |
| if(mfi_dcmd_command(fd, MFI_DCMD_CTRL_GETINFO, info, |
| sizeof(struct mfi_ctrl_info), NULL, 0, statusp) < 0) { |
| return -1; |
| } |
| CONVLE16(&info->pci.vendor); |
| CONVLE16(&info->pci.device); |
| CONVLE16(&info->pci.subvendor); |
| CONVLE16(&info->pci.subdevice); |
| for(i = 0; i < sizeof info->host.port_addr / 8; i++) CONVLE64(info->host.port_addr + i); |
| for(i = 0; i < sizeof info->device.port_addr / 8; i++) CONVLE64(info->device.port_addr + i); |
| CONVLE32(&info->image_component_count); |
| CONVLE32(&info->pending_image_component_count); |
| CONVLE32(&info->hw_present); |
| CONVLE32(&info->current_fw_time); |
| CONVLE16(&info->max_cmds); |
| CONVLE16(&info->max_sg_elements); |
| CONVLE32(&info->max_request_size); |
| CONVLE16(&info->lds_present); |
| CONVLE16(&info->lds_degraded); |
| CONVLE16(&info->lds_offline); |
| CONVLE16(&info->pd_present); |
| CONVLE16(&info->pd_disks_present); |
| CONVLE16(&info->pd_disks_pred_failure); |
| CONVLE16(&info->pd_disks_failed); |
| CONVLE16(&info->nvram_size); |
| CONVLE16(&info->memory_size); |
| CONVLE16(&info->flash_size); |
| CONVLE16(&info->ram_correctable_errors); |
| CONVLE16(&info->ram_uncorrectable_errors); |
| CONVLE16(&info->max_strips_per_io); |
| CONVLE32(&info->raid_levels); |
| CONVLE32(&info->adapter_ops); |
| CONVLE32(&info->ld_ops); |
| CONVLE32(&info->pd_ops); |
| CONVLE32(&info->pd_mix_support); |
| CONVLE16(&info->properties.seq_num); |
| CONVLE16(&info->properties.pred_fail_poll_interval); |
| CONVLE16(&info->properties.intr_throttle_cnt); |
| CONVLE16(&info->properties.intr_throttle_timeout); |
| CONVLE16(&info->properties.ecc_bucket_leak_rate); |
| CONVLE16(&info->properties.spinDownTime); |
| return 0; |
| } |
| |
| int |
| mfi_open(int unit, int acs) |
| { |
| if(unit < 0) { |
| errno = EINVAL; |
| return -1; |
| } |
| #if defined __sun && defined __SVR4 |
| int e = ENOENT; |
| #ifdef HAVE_LIBDEVINFO |
| di_node_t root_node = di_init("/", DINFOSUBTREE); |
| if(root_node == DI_NODE_NIL) return -1; |
| di_node_t node = di_drv_first_node(driver_name, root_node); |
| while(node != DI_NODE_NIL) { |
| if(di_instance(node) == unit) { |
| char *phys_path = di_devfs_path(node); |
| if(!phys_path) { |
| e = errno; |
| break; |
| } |
| size_t phys_path_len = strlen(phys_path); |
| char path[8 + phys_path_len + 21]; |
| sprintf(path, "/devices%s:%d:lsirdctl", phys_path, unit); |
| di_devfs_path_free(phys_path); |
| di_fini(root_node); |
| return open(path, acs); |
| } |
| node = di_drv_next_node(node); |
| } |
| di_fini(root_node); |
| #else |
| FILE *path_to_inst_f = fopen("/etc/path_to_inst", "r"); |
| if(!path_to_inst_f) return -1; |
| char pattern[driver_name_length + 16]; |
| int pattern_len = snprintf(pattern, sizeof pattern, "\" %d \"%s\"", unit, driver_name); |
| char line[4096]; |
| int len; |
| while((len = fgetline(path_to_inst_f, line, sizeof line)) > 0) { |
| if(len <= 1 + pattern_len) continue; |
| size_t phys_path_len = len - pattern_len - 1; |
| if(strcmp(line + 1 + phys_path_len, pattern) == 0) { |
| fclose(path_to_inst_f); |
| size_t unit_s_len = pattern_len - 5 - driver_name_length; |
| char path[8 + phys_path_len + 1 + unit_s_len + 10]; |
| memcpy(path, "/devices", 8); |
| memcpy(path + 8, line + 1, phys_path_len); |
| path[8 + phys_path_len] = ':'; |
| memcpy(path + 8 + phys_path_len + 1, pattern + 2, unit_s_len); |
| strcpy(path + 8 + phys_path_len + 1 + unit_s_len, ":lsirdctl"); |
| return open(path, acs); |
| } |
| } |
| fclose(path_to_inst_f); |
| #endif /* HAVE_LIBDEVINFO */ |
| errno = e; |
| return -1; |
| #else |
| #ifdef HAVE_MFI_DRIVER |
| if(use_mfi) { |
| char path[MAXPATHLEN]; |
| snprintf(path, sizeof(path), "/dev/mfi%d", unit); |
| return (open(path, acs)); |
| } |
| #endif |
| return open("/dev/megaraid_sas_ioctl_node", acs); |
| #endif /* __sun && __SVR4 */ |
| } |
| |
| static void |
| print_time_humanized(unsigned int seconds) |
| { |
| |
| if (seconds > 3600) { |
| printf("%u:", seconds / 3600); |
| } |
| if (seconds > 60) { |
| seconds %= 3600; |
| printf("%02u:%02u", seconds / 60, seconds % 60); |
| } else { |
| printf("%us", seconds); |
| } |
| } |
| |
| void |
| mfi_display_progress(const char *label, const struct mfi_progress *prog) |
| { |
| unsigned int seconds; |
| |
| printf("%s: %.2f%% complete after ", label, |
| (float)prog->progress * 100 / 0xffff); |
| print_time_humanized(prog->elapsed_seconds); |
| if (prog->progress != 0 && prog->elapsed_seconds > 10) { |
| printf(" finished in "); |
| seconds = (0x10000 * (uint32_t)prog->elapsed_seconds) / |
| prog->progress - prog->elapsed_seconds; |
| print_time_humanized(seconds); |
| } |
| printf("\n"); |
| } |
| |
| int |
| mfi_table_handler(struct mfiutil_command **start, struct mfiutil_command **end, |
| int ac, char **av) |
| { |
| struct mfiutil_command **cmd; |
| |
| if (ac < 2) { |
| warnx("The %s command requires a sub-command.", av[0]); |
| return (EINVAL); |
| } |
| for (cmd = start; cmd < end; cmd++) { |
| if (strcmp((*cmd)->name, av[1]) == 0) |
| return ((*cmd)->handler(ac - 1, av + 1)); |
| } |
| |
| warnx("%s is not a valid sub-command of %s.", av[1], av[0]); |
| return (ENOENT); |
| } |