/* * Scsi Host Layer for MPT (Message Passing Technology) based controllers * * Copyright (C) 2012-2016 LSI Corporation * Copyright (C) 2013-2016 Avago Technologies * (mailto: MPT-FusionLinux.pdl@avagotech.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that 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. * * NO WARRANTY * THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT * LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is * solely responsible for determining the appropriateness of using and * distributing the Program and assumes all risks associated with its * exercise of rights under this Agreement, including but not limited to * the risks and costs of program errors, damage to or loss of data, * programs or equipment, and unavailability or interruption of operations. * DISCLAIMER OF LIABILITY * NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED * HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES * 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 Street, Fifth Floor, Boston, MA 02110-1301, * USA. */ #include #include #include #include #include #include "mpt3sas_base.h" /** * _scsih_hide_unhide_sas_devices - add/remove device to/from OS * @ioc: per adapter object * * Return nothing. */ void _scsih_hide_unhide_sas_devices(struct MPT3SAS_ADAPTER *ioc) { struct _sas_device *sas_device, *sas_device_next; if (!ioc->is_warpdrive || ioc->mfg_pg10_hide_flag != MFG_PAGE10_HIDE_IF_VOL_PRESENT) return; if (ioc->hide_drives) { if (mpt3sas_get_num_volumes(ioc)) return; ioc->hide_drives = 0; list_for_each_entry_safe(sas_device, sas_device_next, &ioc->sas_device_list, list) { if (!mpt3sas_transport_port_add(ioc, sas_device->handle, sas_device->sas_address_parent, sas_device->port)) { _scsih_sas_device_remove(ioc, sas_device); } else if (!sas_device->starget) { mpt3sas_transport_port_remove(ioc, sas_device->sas_address, sas_device->sas_address_parent, sas_device->port); _scsih_sas_device_remove(ioc, sas_device); } } } else { if (!mpt3sas_get_num_volumes(ioc)) return; ioc->hide_drives = 1; list_for_each_entry_safe(sas_device, sas_device_next, &ioc->sas_device_list, list) { mpt3sas_transport_port_remove(ioc, sas_device->sas_address, sas_device->sas_address_parent, sas_device->port); } } } /** * _warpdrive_disable_ddio - Disable direct I/O for all the volumes * @ioc: per adapter object */ static void _warpdrive_disable_ddio(struct MPT3SAS_ADAPTER *ioc) { Mpi2RaidVolPage1_t vol_pg1; Mpi2ConfigReply_t mpi_reply; struct _raid_device *raid_device; u16 handle; u16 ioc_status; unsigned long flags; handle = 0xFFFF; while (!(mpt3sas_config_get_raid_volume_pg1(ioc, &mpi_reply, &vol_pg1, MPI2_RAID_VOLUME_PGAD_FORM_GET_NEXT_HANDLE, handle))) { ioc_status = le16_to_cpu(mpi_reply.IOCStatus) & MPI2_IOCSTATUS_MASK; if (ioc_status == MPI2_IOCSTATUS_CONFIG_INVALID_PAGE) break; handle = le16_to_cpu(vol_pg1.DevHandle); spin_lock_irqsave(&ioc->raid_device_lock, flags); raid_device = mpt3sas_raid_device_find_by_handle(ioc, handle); if (raid_device) raid_device->direct_io_enabled = 0; spin_unlock_irqrestore(&ioc->raid_device_lock, flags); } return; } /** * mpt3sas_get_num_volumes - Get number of volumes in the ioc * @ioc: per adapter object */ u8 mpt3sas_get_num_volumes(struct MPT3SAS_ADAPTER *ioc) { Mpi2RaidVolPage1_t vol_pg1; Mpi2ConfigReply_t mpi_reply; u16 handle; u8 vol_cnt = 0; u16 ioc_status; handle = 0xFFFF; while (!(mpt3sas_config_get_raid_volume_pg1(ioc, &mpi_reply, &vol_pg1, MPI2_RAID_VOLUME_PGAD_FORM_GET_NEXT_HANDLE, handle))) { ioc_status = le16_to_cpu(mpi_reply.IOCStatus) & MPI2_IOCSTATUS_MASK; if (ioc_status == MPI2_IOCSTATUS_CONFIG_INVALID_PAGE) break; vol_cnt++; handle = le16_to_cpu(vol_pg1.DevHandle); } return vol_cnt; } /** * mpt3sas_init_warpdrive_properties - Set properties for warpdrive direct I/O. * @ioc: per adapter object * @raid_device: the raid_device object */ void mpt3sas_init_warpdrive_properties(struct MPT3SAS_ADAPTER *ioc, struct _raid_device *raid_device) { Mpi2RaidVolPage0_t *vol_pg0; Mpi2RaidPhysDiskPage0_t pd_pg0; Mpi2ConfigReply_t mpi_reply; u16 sz; u8 num_pds, count; unsigned long stripe_sz, block_sz; u8 stripe_exp, block_exp; u64 dev_max_lba; if (!ioc->is_warpdrive) return; if (ioc->mfg_pg10_hide_flag == MFG_PAGE10_EXPOSE_ALL_DISKS) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is disabled " "globally as drives are exposed\n", ioc->name); return; } if (mpt3sas_get_num_volumes(ioc) > 1) { _warpdrive_disable_ddio(ioc); printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is disabled " "globally as number of drives > 1\n", ioc->name); return; } if ((mpt3sas_config_get_number_pds(ioc, raid_device->handle, &num_pds)) || !num_pds) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is disabled " "Failure in computing number of drives\n", ioc->name); return; } sz = offsetof(Mpi2RaidVolPage0_t, PhysDisk) + (num_pds * sizeof(Mpi2RaidVol0PhysDisk_t)); vol_pg0 = kzalloc(sz, GFP_KERNEL); if (!vol_pg0) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is disabled " "Memory allocation failure for RVPG0\n", ioc->name); return; } if ((mpt3sas_config_get_raid_volume_pg0(ioc, &mpi_reply, vol_pg0, MPI2_RAID_VOLUME_PGAD_FORM_HANDLE, raid_device->handle, sz))) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is disabled " "Failure in retrieving RVPG0\n", ioc->name); kfree(vol_pg0); return; } /* * WARPDRIVE:If number of physical disks in a volume exceeds the max pds * assumed for WARPDRIVE, disable direct I/O */ if (num_pds > MPT_MAX_WARPDRIVE_PDS) { printk(MPT3SAS_WARN_FMT "WarpDrive : Direct IO is disabled " "for the drive with handle(0x%04x): num_mem=%d, " "max_mem_allowed=%d\n", ioc->name, raid_device->handle, num_pds, MPT_MAX_WARPDRIVE_PDS); kfree(vol_pg0); return; } for (count = 0; count < num_pds; count++) { if (mpt3sas_config_get_phys_disk_pg0(ioc, &mpi_reply, &pd_pg0, MPI2_PHYSDISK_PGAD_FORM_PHYSDISKNUM, vol_pg0->PhysDisk[count].PhysDiskNum) || le16_to_cpu(pd_pg0.DevHandle) == MPT3SAS_INVALID_DEVICE_HANDLE) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is " "disabled for the drive with handle(0x%04x) member" "handle retrieval failed for member number=%d\n", ioc->name, raid_device->handle, vol_pg0->PhysDisk[count].PhysDiskNum); goto out_error; } /* Disable direct I/O if member drive lba exceeds 4 bytes */ dev_max_lba = le64_to_cpu(pd_pg0.DeviceMaxLBA); if (dev_max_lba >> 32) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is " "disabled for the drive with handle(0x%04x) member" "handle (0x%04x) unsupported max lba 0x%016llx\n", ioc->name, raid_device->handle, le16_to_cpu(pd_pg0.DevHandle), (unsigned long long)dev_max_lba); goto out_error; } raid_device->pd_handle[count] = le16_to_cpu(pd_pg0.DevHandle); } /* * Assumption for WD: Direct I/O is not supported if the volume is * not RAID0 */ if (raid_device->volume_type != MPI2_RAID_VOL_TYPE_RAID0) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is disabled " "for the drive with handle(0x%04x): type=%d, " "s_sz=%uK, blk_size=%u\n", ioc->name, raid_device->handle, raid_device->volume_type, (le32_to_cpu(vol_pg0->StripeSize) * le16_to_cpu(vol_pg0->BlockSize)) / 1024, le16_to_cpu(vol_pg0->BlockSize)); goto out_error; } stripe_sz = le32_to_cpu(vol_pg0->StripeSize); stripe_exp = find_first_bit(&stripe_sz, 32); if (stripe_exp == 32) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is disabled " "for the drive with handle(0x%04x) invalid stripe sz %uK\n", ioc->name, raid_device->handle, (le32_to_cpu(vol_pg0->StripeSize) * le16_to_cpu(vol_pg0->BlockSize)) / 1024); goto out_error; } raid_device->stripe_exponent = stripe_exp; block_sz = le16_to_cpu(vol_pg0->BlockSize); block_exp = find_first_bit(&block_sz, 16); if (block_exp == 16) { printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is disabled " "for the drive with handle(0x%04x) invalid block sz %u\n", ioc->name, raid_device->handle, le16_to_cpu(vol_pg0->BlockSize)); goto out_error; } raid_device->block_exponent = block_exp; raid_device->direct_io_enabled = 1; printk(MPT3SAS_INFO_FMT "WarpDrive : Direct IO is Enabled for the drive" " with handle(0x%04x)\n", ioc->name, raid_device->handle); /* * WARPDRIVE: Though the following fields are not used for direct IO, * stored for future purpose: */ raid_device->max_lba = le64_to_cpu(vol_pg0->MaxLBA); raid_device->stripe_sz = le32_to_cpu(vol_pg0->StripeSize); raid_device->block_sz = le16_to_cpu(vol_pg0->BlockSize); kfree(vol_pg0); return; out_error: raid_device->direct_io_enabled = 0; for (count = 0; count < num_pds; count++) raid_device->pd_handle[count] = 0; kfree(vol_pg0); return; } /** * _scsih_setup_direct_io - setup MPI request for WARPDRIVE Direct I/O * @ioc: per adapter object * @scmd: pointer to scsi command object * @raid_device: pointer to raid device data structure * @mpi_request: pointer to the SCSI_IO reqest message frame * @smid: system request message index * * Returns nothing */ void mpt3sas_setup_direct_io(struct MPT3SAS_ADAPTER *ioc, struct scsi_cmnd *scmd, struct _raid_device *raid_device, Mpi25SCSIIORequest_t *mpi_request) { u32 p_lba, stripe_off, stripe_unit, column, io_size; u64 v_lba; u32 stripe_sz, stripe_exp; u8 num_pds, *cdb_ptr, i; u8 cdb0 = scmd->cmnd[0]; u64 v_llba; struct scsiio_tracker *st = mpt3sas_base_scsi_cmd_priv(scmd); /* * Try Direct I/O to RAID memeber disks */ if (cdb0 == READ_16 || cdb0 == READ_10 || cdb0 == WRITE_16 || cdb0 == WRITE_10) { cdb_ptr = mpi_request->CDB.CDB32; if ((cdb0 < READ_16) || !(cdb_ptr[2] | cdb_ptr[3] | cdb_ptr[4] | cdb_ptr[5])) { #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)) io_size = scmd->request_bufflen >> raid_device->block_exponent; #else io_size = scsi_bufflen(scmd) >> raid_device->block_exponent; #endif /* get virtual lba */ if ((cdb0 < READ_16)) { v_lba = be32_to_cpu(*(__be32 *)(&cdb_ptr[2])); } else { v_lba = be64_to_cpu(*(__be64 *)(&cdb_ptr[2])); } i = (cdb0 < READ_16) ? 2 : 6; if (((u64)v_lba + (u64)io_size - 1) <= raid_device->max_lba) { stripe_sz = raid_device->stripe_sz; stripe_exp = raid_device->stripe_exponent; stripe_off = v_lba & (stripe_sz - 1); /* Check whether IO falls within a stripe */ if ((stripe_off + io_size) <= stripe_sz) { num_pds = raid_device->num_pds; p_lba = v_lba >> stripe_exp; stripe_unit = p_lba / num_pds; column = p_lba % num_pds; p_lba = (stripe_unit << stripe_exp) + stripe_off; mpi_request->DevHandle = cpu_to_le16(raid_device-> pd_handle[column]); (*(__be32 *)(&cdb_ptr[i])) = cpu_to_be32(p_lba); /* * WD: To indicate this I/O is directI/O */ st->direct_io = 1; #ifdef MPT2SAS_WD_DDIOCOUNT ioc->ddio_count++; #endif #ifdef MPT2SAS_WD_LOGGING if (ioc->logging_level & MPT_DEBUG_SCSI) { printk(MPT3SAS_INFO_FMT "scmd(%p) as direct IO\n", ioc->name, scmd); scsi_print_command(scmd); } #endif } } } else { #if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)) io_size = scmd->request_bufflen >> raid_device->block_exponent; #else io_size = scsi_bufflen(scmd) >> raid_device->block_exponent; #endif /* get virtual lba */ v_llba = be64_to_cpu(*(__be64 *)(&cdb_ptr[2])); if ((v_llba + (u64)io_size - 1) <= raid_device->max_lba) { stripe_sz = raid_device->stripe_sz; stripe_exp = raid_device->stripe_exponent; stripe_off = (u32) (v_llba & (stripe_sz - 1)); /* Check whether IO falls within a stripe */ if ((stripe_off + io_size) <= stripe_sz) { num_pds = raid_device->num_pds; p_lba = (u32)(v_llba >> stripe_exp); stripe_unit = p_lba / num_pds; column = p_lba % num_pds; p_lba = (stripe_unit << stripe_exp) + stripe_off; mpi_request->DevHandle = cpu_to_le16(raid_device-> pd_handle[column]); (*(__be64 *)(&cdb_ptr[2])) = cpu_to_be64((u64)p_lba); /* * WD: To indicate this I/O is directI/O */ st->direct_io = 1; #ifdef MPT2SAS_WD_DDIOCOUNT ioc->ddio_count++; #endif #ifdef MPT2SAS_WD_LOGGING if (ioc->logging_level & MPT_DEBUG_SCSI) { printk(MPT3SAS_INFO_FMT "scmd(%p) as direct IO\n", ioc->name, scmd); scsi_print_command(scmd); } #endif } } } } } void _scsih_log_entry_add_event(struct MPT3SAS_ADAPTER *ioc, Mpi2EventDataLogEntryAdded_t *log_entry) { __le32 *log_code; if (!ioc->is_warpdrive) return; log_code = (__le32 *)log_entry->LogData; mpt3sas_trigger_event(ioc, MPI2_EVENT_LOG_ENTRY_ADDED, le16_to_cpu(log_entry->LogEntryQualifier)); if (le16_to_cpu(log_entry->LogEntryQualifier) != MPT2_WARPDRIVE_LOGENTRY) return; switch (le32_to_cpu(*log_code)) { case MPT2_WARPDRIVE_LC_SSDT: printk(MPT3SAS_WARN_FMT "WarpDrive Warning: " "IO Throttling has occurred in the WarpDrive " "subsystem. Check WarpDrive documentation for " "additional details.\n", ioc->name); break; case MPT2_WARPDRIVE_LC_SSDLW: printk(MPT3SAS_WARN_FMT "WarpDrive Warning: " "Program/Erase Cycles for the WarpDrive subsystem " "in degraded range. Check WarpDrive documentation " "for additional details.\n", ioc->name); break; case MPT2_WARPDRIVE_LC_SSDLF: printk(MPT3SAS_ERR_FMT "WarpDrive Fatal Error: " "There are no Program/Erase Cycles for the " "WarpDrive subsystem. The storage device will be " "in read-only mode. Check WarpDrive documentation " "for additional details.\n", ioc->name); break; case MPT2_WARPDRIVE_LC_BRMF: printk(MPT3SAS_ERR_FMT "WarpDrive Fatal Error: " "The Backup Rail Monitor has failed on the " "WarpDrive subsystem. Check WarpDrive " "documentation for additional details.\n", ioc->name); break; } } #ifdef MPT2SAS_WD_DDIOCOUNT #if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,25)) ssize_t _ctl_ioc_ddio_count_show(struct device *cdev, struct device_attribute *attr, char *buf) #else ssize_t _ctl_ioc_ddio_count_show(struct class_device *cdev, char *buf) #endif { struct Scsi_Host *shost = class_to_shost(cdev); struct MPT3SAS_ADAPTER *ioc = shost_private(shost); return snprintf(buf, PAGE_SIZE, "0x%016llx\n", (unsigned long long)ioc->ddio_count); } #if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,25)) ssize_t _ctl_ioc_ddio_err_count_show(struct device *cdev, struct device_attribute *attr, char *buf) #else ssize_t _ctl_ioc_ddio_err_count_show(struct class_device *cdev, char *buf) #endif { struct Scsi_Host *shost = class_to_shost(cdev); struct MPT3SAS_ADAPTER *ioc = shost_private(shost); return snprintf(buf, PAGE_SIZE, "0x%016llx\n", (unsigned long long) ioc->ddio_err_count); } #endif /** * _ctl_BRM_status_show - Backup Rail Monitor Status * @cdev - pointer to embedded class device * @buf - the buffer returned * * This is number of reply queues * * A sysfs 'read-only' shost attribute. */ #if (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,25)) ssize_t _ctl_BRM_status_show(struct device *cdev, struct device_attribute *attr, char *buf) #else ssize_t _ctl_BRM_status_show(struct class_device *cdev, char *buf) #endif { struct Scsi_Host *shost = class_to_shost(cdev); struct MPT3SAS_ADAPTER *ioc = shost_private(shost); Mpi2IOUnitPage3_t *io_unit_pg3 = NULL; Mpi2ConfigReply_t mpi_reply; u16 backup_rail_monitor_status = 0; u16 ioc_status; int sz; ssize_t rc = 0; if (!ioc->is_warpdrive) { printk(MPT3SAS_ERR_FMT "%s: BRM attribute is only for " "warpdrive\n", ioc->name, __func__); return 0; } else { if (ioc->pci_error_recovery || ioc->remove_host) { return 0; } } mutex_lock(&ioc->pci_access_mutex); /* allocate upto GPIOVal 36 entries */ sz = offsetof(Mpi2IOUnitPage3_t, GPIOVal) + (sizeof(u16) * 36); io_unit_pg3 = kzalloc(sz, GFP_KERNEL); if (!io_unit_pg3) { printk(MPT3SAS_ERR_FMT "%s: failed allocating memory " "for iounit_pg3: (%d) bytes\n", ioc->name, __func__, sz); goto out; } if (mpt3sas_config_get_iounit_pg3(ioc, &mpi_reply, io_unit_pg3, sz) != 0) { printk(MPT3SAS_ERR_FMT "%s: failed reading iounit_pg3\n", ioc->name, __func__); goto out; } ioc_status = le16_to_cpu(mpi_reply.IOCStatus) & MPI2_IOCSTATUS_MASK; if (ioc_status != MPI2_IOCSTATUS_SUCCESS) { printk(MPT3SAS_ERR_FMT "%s: iounit_pg3 failed with " "ioc_status(0x%04x)\n", ioc->name, __func__, ioc_status); goto out; } if (io_unit_pg3->GPIOCount < 25) { printk(MPT3SAS_ERR_FMT "%s: iounit_pg3->GPIOCount less than" " 25 entries, detected (%d) entries\n", ioc->name, __func__, io_unit_pg3->GPIOCount); goto out; } /* BRM status is in bit zero of GPIOVal[24] */ backup_rail_monitor_status = le16_to_cpu(io_unit_pg3->GPIOVal[24]); rc = snprintf(buf, PAGE_SIZE, "%d\n", (backup_rail_monitor_status & 1)); out: kfree(io_unit_pg3); mutex_unlock(&ioc->pci_access_mutex); return rc; }