/* * Intel(R) Enclosure LED Utilities * Copyright (C) 2009-2020 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 #if _HAVE_DMALLOC_H #include #endif #include "ahci.h" #include "block.h" #include "config.h" #include "dellssd.h" #include "pci_slot.h" #include "raid.h" #include "scsi.h" #include "slave.h" #include "smp.h" #include "status.h" #include "sysfs.h" #include "utils.h" #include "vmdssd.h" #include "npem.h" #include "amd.h" /* Global timestamp value. It shell be used to update a timestamp field of block device structure. See block.h for details. */ time_t timestamp = 0; /** * @brief Determines if disk is attached directly or via expander */ int dev_directly_attached(const char *path) { if (strstr(path, "/expander") == 0) return 1; return 0; } /** * @brief Determines a send function. * * This is the internal function of 'block device' module. The function tries to * determine a LED management protocol based on controller type and the given * path to block device in sysfs tree. First it checks whether to use * the default send function. If not it tries to read the content * of em_message_type field from sysfs tree and determines * the LED control protocol. * * @param[in] cntrl type of a controller a device is connected to. * @param[in] path path to a block device in sysfs tree. * * @return Pointer to send message function if successful, otherwise the function * returns the NULL pointer and it means either the controller does not * support enclosure management or LED control protocol * is not supported. */ static send_message_t _get_send_fn(struct cntrl_device *cntrl, const char *path) { send_message_t result = NULL; if (cntrl->cntrl_type == CNTRL_TYPE_AHCI) { result = ahci_sgpio_write; } else if (cntrl->cntrl_type == CNTRL_TYPE_SCSI && !dev_directly_attached(path)) { result = scsi_ses_write; } else if (cntrl->cntrl_type == CNTRL_TYPE_SCSI && dev_directly_attached(path)) { result = scsi_smp_fill_buffer; } else if (cntrl->cntrl_type == CNTRL_TYPE_DELLSSD) { result = dellssd_write; } else if (cntrl->cntrl_type == CNTRL_TYPE_VMD) { result = vmdssd_write; } else if (cntrl->cntrl_type == CNTRL_TYPE_NPEM) { result = npem_write; } else if (cntrl->cntrl_type == CNTRL_TYPE_AMD) { result = amd_write; } return result; } static int do_not_flush(struct block_device *device __attribute__ ((unused))) { return 1; } static flush_message_t _get_flush_fn(struct cntrl_device *cntrl, const char *path) { flush_message_t result = NULL; if (cntrl->cntrl_type == CNTRL_TYPE_SCSI) { if (dev_directly_attached(path)) result = scsi_smp_write_buffer; else result = scsi_ses_flush; } else { result = do_not_flush; } return result; } /** * @brief Determines a host path to block device. * * This is the internal function of 'block device' module. The function * determines a host path to block device in sysfs. * * @param[in] path path to block device in sysfs. * @param[in] cntrl controller device the block * device is connected to. * * @return Pointer to memory block containing a host path. The memory block * should be freed if one don't need the content. */ static char *_get_host(char *path, struct cntrl_device *cntrl) { char *result = NULL; if (cntrl->cntrl_type == CNTRL_TYPE_SCSI) result = scsi_get_slot_path(path, cntrl->sysfs_path); else if (cntrl->cntrl_type == CNTRL_TYPE_AHCI) result = ahci_get_port_path(path); else if (cntrl->cntrl_type == CNTRL_TYPE_DELLSSD) result = dellssd_get_path(cntrl->sysfs_path); else if (cntrl->cntrl_type == CNTRL_TYPE_VMD) result = vmdssd_get_path(cntrl->sysfs_path); else if (cntrl->cntrl_type == CNTRL_TYPE_NPEM) result = npem_get_path(cntrl->sysfs_path); else if (cntrl->cntrl_type == CNTRL_TYPE_AMD) result = amd_get_path(path, cntrl->sysfs_path); return result; } static int is_host_id_supported(const struct block_device *bd) { if (!bd->cntrl) return 0; switch (bd->cntrl->cntrl_type) { case CNTRL_TYPE_DELLSSD: case CNTRL_TYPE_VMD: case CNTRL_TYPE_NPEM: return 0; default: return 1; } } /** * @brief Determines a storage controller. * * This is the internal function of 'block device' module. The function gets * a pointer to controller structure the device is connected to. * * @param[in] cntrl_list pointer to list of supported controllers. * @param[in] path path to block device in sysfs tree. * * @return Pointer to controller structure if successful, otherwise the function * returns NULL pointer. The NULL pointer means that block devices is * connected to unsupported storage controller. */ struct cntrl_device *block_get_controller(const struct list *cntrl_list, char *path) { struct cntrl_device *cntrl; struct cntrl_device *non_npem_cntrl = NULL; list_for_each(cntrl_list, cntrl) { if (strncmp(cntrl->sysfs_path, path, strlen(cntrl->sysfs_path)) == 0) { if (cntrl->cntrl_type == CNTRL_TYPE_NPEM) return cntrl; non_npem_cntrl = cntrl; } } return non_npem_cntrl; } struct _host_type *block_get_host(struct cntrl_device *cntrl, int host_id) { struct _host_type *hosts = NULL; if (!cntrl) return hosts; hosts = cntrl->hosts; while (hosts) { if (hosts->host_id == host_id) break; hosts = hosts->next; } return hosts; } /* * Allocates a new block device structure. See block.h for details. */ struct block_device *block_device_init(const struct list *cntrl_list, const char *path) { struct cntrl_device *cntrl; char link[PATH_MAX]; char *host = NULL; struct block_device *device = NULL; struct pci_slot *pci_slot = NULL; send_message_t send_fn = NULL; flush_message_t flush_fn = NULL; int host_id = -1; char *host_name; if (realpath(path, link)) { pci_slot = vmdssd_find_pci_slot(link); cntrl = block_get_controller(cntrl_list, link); if (cntrl != NULL) { if (cntrl->cntrl_type == CNTRL_TYPE_VMD && !pci_slot) return NULL; host = _get_host(link, cntrl); if (host == NULL) return NULL; host_name = get_path_hostN(link); if (host_name) { if (sscanf(host_name, "host%d", &host_id) != 1) host_id = -1; free(host_name); } flush_fn = _get_flush_fn(cntrl, link); send_fn = _get_send_fn(cntrl, link); if (send_fn == NULL) { free(host); return NULL; } } else { return NULL; } device = calloc(1, sizeof(*device)); if (device) { struct _host_type *hosts = cntrl ? cntrl->hosts : NULL; device->cntrl = cntrl; device->sysfs_path = str_dup(link); device->cntrl_path = host; device->ibpi = IBPI_PATTERN_UNKNOWN; device->ibpi_prev = IBPI_PATTERN_NONE; device->send_fn = send_fn; device->flush_fn = flush_fn; device->timestamp = timestamp; device->host = NULL; device->host_id = host_id; device->encl_index = -1; device->raid_dev = NULL; while (hosts) { if (hosts->host_id == host_id) { device->host = hosts; break; } hosts = hosts->next; } if (cntrl && cntrl->cntrl_type == CNTRL_TYPE_SCSI) { device->phy_index = cntrl_init_smp(link, cntrl); if (!dev_directly_attached(link) && !scsi_get_enclosure(device)) { log_debug("Device initialization failed for '%s'", path); free(device->sysfs_path); free(device->cntrl_path); free(device); device = NULL; } } } else if (host) { free(host); } } return device; } /** * Frees memory allocated for block device structure. See block.h for details. */ void block_device_fini(struct block_device *device) { if (device) { if (device->sysfs_path) free(device->sysfs_path); if (device->cntrl_path) free(device->cntrl_path); if (device->raid_dev) raid_device_fini(device->raid_dev); free(device); } } /* * Duplicates a block device structure. See block.h for details. */ struct block_device *block_device_duplicate(struct block_device *block) { struct block_device *result = NULL; if (block) { result = calloc(1, sizeof(*result)); if (result) { result->sysfs_path = str_dup(block->sysfs_path); result->cntrl_path = str_dup(block->cntrl_path); if (block->ibpi != IBPI_PATTERN_UNKNOWN) result->ibpi = block->ibpi; else result->ibpi = IBPI_PATTERN_ONESHOT_NORMAL; result->ibpi_prev = block->ibpi_prev; result->send_fn = block->send_fn; result->flush_fn = block->flush_fn; result->timestamp = block->timestamp; result->cntrl = block->cntrl; result->host = block->host; result->host_id = block->host_id; result->phy_index = block->phy_index; result->encl_index = block->encl_index; result->enclosure = block->enclosure; result->raid_dev = raid_device_duplicate(block->raid_dev); } } return result; } int block_compare(const struct block_device *bd_old, const struct block_device *bd_new) { int i = 0; if (is_host_id_supported(bd_old) && bd_old->host_id == -1) { log_debug("Device %s : No host_id!", strstr(bd_old->sysfs_path, "host")); return 0; } if (is_host_id_supported(bd_new) && bd_new->host_id == -1) { log_debug("Device %s : No host_id!", strstr(bd_new->sysfs_path, "host")); return 0; } if (bd_old->cntrl->cntrl_type != bd_new->cntrl->cntrl_type) return 0; switch (bd_old->cntrl->cntrl_type) { case CNTRL_TYPE_AHCI: /* Missing support for port multipliers. Compare just hostX. */ i = (bd_old->host_id == bd_new->host_id); break; case CNTRL_TYPE_SCSI: /* Host and phy is not enough. They might be DA or EA. */ if (dev_directly_attached(bd_old->sysfs_path) && dev_directly_attached(bd_new->sysfs_path)) { /* Just compare host & phy */ i = (bd_old->host_id == bd_new->host_id) && (bd_old->phy_index == bd_new->phy_index); break; } if (!dev_directly_attached(bd_old->sysfs_path) && !dev_directly_attached(bd_new->sysfs_path)) { /* Both expander attached */ i = (bd_old->host_id == bd_new->host_id) && (bd_old->phy_index == bd_new->phy_index); i = i && (bd_old->enclosure == bd_new->enclosure); i = i && (bd_old->encl_index == bd_new->encl_index); break; } /* */ break; case CNTRL_TYPE_VMD: /* compare names and address of the drive */ i = (strcmp(bd_old->sysfs_path, bd_new->sysfs_path) == 0); if (!i) { struct pci_slot *old_slot, *new_slot; old_slot = vmdssd_find_pci_slot(bd_old->sysfs_path); new_slot = vmdssd_find_pci_slot(bd_new->sysfs_path); if (old_slot && new_slot) i = (strcmp(old_slot->address, new_slot->address) == 0); } break; case CNTRL_TYPE_NPEM: /* check controller to determine slot. */ i = (strcmp(bd_old->cntrl_path, bd_new->cntrl_path) == 0); break; case CNTRL_TYPE_DELLSSD: default: /* Just compare names */ i = (strcmp(bd_old->sysfs_path, bd_new->sysfs_path) == 0); break; } return i; }