X-Git-Url: https://gerrit.opnfv.org/gerrit/gitweb?a=blobdiff_plain;f=kernel%2Fdrivers%2Fmessage%2Ffusion%2Fmptsas.c;fp=kernel%2Fdrivers%2Fmessage%2Ffusion%2Fmptsas.c;h=5bdaae15a74257d9c252abed3b15ae5ce5cde69a;hb=9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00;hp=0000000000000000000000000000000000000000;hpb=98260f3884f4a202f9ca5eabed40b1354c489b29;p=kvmfornfv.git diff --git a/kernel/drivers/message/fusion/mptsas.c b/kernel/drivers/message/fusion/mptsas.c new file mode 100644 index 000000000..5bdaae15a --- /dev/null +++ b/kernel/drivers/message/fusion/mptsas.c @@ -0,0 +1,5444 @@ +/* + * linux/drivers/message/fusion/mptsas.c + * For use with LSI PCI chip/adapter(s) + * running LSI Fusion MPT (Message Passing Technology) firmware. + * + * Copyright (c) 1999-2008 LSI Corporation + * (mailto:DL-MPTFusionLinux@lsi.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; version 2 of the License. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ + +#include +#include +#include +#include +#include +#include +#include +#include /* for mdelay */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mptbase.h" +#include "mptscsih.h" +#include "mptsas.h" + + +#define my_NAME "Fusion MPT SAS Host driver" +#define my_VERSION MPT_LINUX_VERSION_COMMON +#define MYNAM "mptsas" + +/* + * Reserved channel for integrated raid + */ +#define MPTSAS_RAID_CHANNEL 1 + +#define SAS_CONFIG_PAGE_TIMEOUT 30 +MODULE_AUTHOR(MODULEAUTHOR); +MODULE_DESCRIPTION(my_NAME); +MODULE_LICENSE("GPL"); +MODULE_VERSION(my_VERSION); + +static int mpt_pt_clear; +module_param(mpt_pt_clear, int, 0); +MODULE_PARM_DESC(mpt_pt_clear, + " Clear persistency table: enable=1 " + "(default=MPTSCSIH_PT_CLEAR=0)"); + +/* scsi-mid layer global parmeter is max_report_luns, which is 511 */ +#define MPTSAS_MAX_LUN (16895) +static int max_lun = MPTSAS_MAX_LUN; +module_param(max_lun, int, 0); +MODULE_PARM_DESC(max_lun, " max lun, default=16895 "); + +static int mpt_loadtime_max_sectors = 8192; +module_param(mpt_loadtime_max_sectors, int, 0); +MODULE_PARM_DESC(mpt_loadtime_max_sectors, + " Maximum sector define for Host Bus Adaptor.Range 64 to 8192 default=8192"); + +static u8 mptsasDoneCtx = MPT_MAX_PROTOCOL_DRIVERS; +static u8 mptsasTaskCtx = MPT_MAX_PROTOCOL_DRIVERS; +static u8 mptsasInternalCtx = MPT_MAX_PROTOCOL_DRIVERS; /* Used only for internal commands */ +static u8 mptsasMgmtCtx = MPT_MAX_PROTOCOL_DRIVERS; +static u8 mptsasDeviceResetCtx = MPT_MAX_PROTOCOL_DRIVERS; + +static void mptsas_firmware_event_work(struct work_struct *work); +static void mptsas_send_sas_event(struct fw_event_work *fw_event); +static void mptsas_send_raid_event(struct fw_event_work *fw_event); +static void mptsas_send_ir2_event(struct fw_event_work *fw_event); +static void mptsas_parse_device_info(struct sas_identify *identify, + struct mptsas_devinfo *device_info); +static inline void mptsas_set_rphy(MPT_ADAPTER *ioc, + struct mptsas_phyinfo *phy_info, struct sas_rphy *rphy); +static struct mptsas_phyinfo *mptsas_find_phyinfo_by_sas_address + (MPT_ADAPTER *ioc, u64 sas_address); +static int mptsas_sas_device_pg0(MPT_ADAPTER *ioc, + struct mptsas_devinfo *device_info, u32 form, u32 form_specific); +static int mptsas_sas_enclosure_pg0(MPT_ADAPTER *ioc, + struct mptsas_enclosure *enclosure, u32 form, u32 form_specific); +static int mptsas_add_end_device(MPT_ADAPTER *ioc, + struct mptsas_phyinfo *phy_info); +static void mptsas_del_end_device(MPT_ADAPTER *ioc, + struct mptsas_phyinfo *phy_info); +static void mptsas_send_link_status_event(struct fw_event_work *fw_event); +static struct mptsas_portinfo *mptsas_find_portinfo_by_sas_address + (MPT_ADAPTER *ioc, u64 sas_address); +static void mptsas_expander_delete(MPT_ADAPTER *ioc, + struct mptsas_portinfo *port_info, u8 force); +static void mptsas_send_expander_event(struct fw_event_work *fw_event); +static void mptsas_not_responding_devices(MPT_ADAPTER *ioc); +static void mptsas_scan_sas_topology(MPT_ADAPTER *ioc); +static void mptsas_broadcast_primative_work(struct fw_event_work *fw_event); +static void mptsas_handle_queue_full_event(struct fw_event_work *fw_event); +static void mptsas_volume_delete(MPT_ADAPTER *ioc, u8 id); +void mptsas_schedule_target_reset(void *ioc); + +static void mptsas_print_phy_data(MPT_ADAPTER *ioc, + MPI_SAS_IO_UNIT0_PHY_DATA *phy_data) +{ + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "---- IO UNIT PAGE 0 ------------\n", ioc->name)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Handle=0x%X\n", + ioc->name, le16_to_cpu(phy_data->AttachedDeviceHandle))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Controller Handle=0x%X\n", + ioc->name, le16_to_cpu(phy_data->ControllerDevHandle))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Port=0x%X\n", + ioc->name, phy_data->Port)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Port Flags=0x%X\n", + ioc->name, phy_data->PortFlags)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "PHY Flags=0x%X\n", + ioc->name, phy_data->PhyFlags)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Negotiated Link Rate=0x%X\n", + ioc->name, phy_data->NegotiatedLinkRate)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Controller PHY Device Info=0x%X\n", ioc->name, + le32_to_cpu(phy_data->ControllerPhyDeviceInfo))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "DiscoveryStatus=0x%X\n\n", + ioc->name, le32_to_cpu(phy_data->DiscoveryStatus))); +} + +static void mptsas_print_phy_pg0(MPT_ADAPTER *ioc, SasPhyPage0_t *pg0) +{ + __le64 sas_address; + + memcpy(&sas_address, &pg0->SASAddress, sizeof(__le64)); + + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "---- SAS PHY PAGE 0 ------------\n", ioc->name)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Attached Device Handle=0x%X\n", ioc->name, + le16_to_cpu(pg0->AttachedDevHandle))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "SAS Address=0x%llX\n", + ioc->name, (unsigned long long)le64_to_cpu(sas_address))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Attached PHY Identifier=0x%X\n", ioc->name, + pg0->AttachedPhyIdentifier)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Attached Device Info=0x%X\n", + ioc->name, le32_to_cpu(pg0->AttachedDeviceInfo))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Programmed Link Rate=0x%X\n", + ioc->name, pg0->ProgrammedLinkRate)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Change Count=0x%X\n", + ioc->name, pg0->ChangeCount)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "PHY Info=0x%X\n\n", + ioc->name, le32_to_cpu(pg0->PhyInfo))); +} + +static void mptsas_print_phy_pg1(MPT_ADAPTER *ioc, SasPhyPage1_t *pg1) +{ + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "---- SAS PHY PAGE 1 ------------\n", ioc->name)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Invalid Dword Count=0x%x\n", + ioc->name, pg1->InvalidDwordCount)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Running Disparity Error Count=0x%x\n", ioc->name, + pg1->RunningDisparityErrorCount)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Loss Dword Synch Count=0x%x\n", ioc->name, + pg1->LossDwordSynchCount)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "PHY Reset Problem Count=0x%x\n\n", ioc->name, + pg1->PhyResetProblemCount)); +} + +static void mptsas_print_device_pg0(MPT_ADAPTER *ioc, SasDevicePage0_t *pg0) +{ + __le64 sas_address; + + memcpy(&sas_address, &pg0->SASAddress, sizeof(__le64)); + + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "---- SAS DEVICE PAGE 0 ---------\n", ioc->name)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Handle=0x%X\n", + ioc->name, le16_to_cpu(pg0->DevHandle))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Parent Handle=0x%X\n", + ioc->name, le16_to_cpu(pg0->ParentDevHandle))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Enclosure Handle=0x%X\n", + ioc->name, le16_to_cpu(pg0->EnclosureHandle))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Slot=0x%X\n", + ioc->name, le16_to_cpu(pg0->Slot))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "SAS Address=0x%llX\n", + ioc->name, (unsigned long long)le64_to_cpu(sas_address))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Target ID=0x%X\n", + ioc->name, pg0->TargetID)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Bus=0x%X\n", + ioc->name, pg0->Bus)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Parent Phy Num=0x%X\n", + ioc->name, pg0->PhyNum)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Access Status=0x%X\n", + ioc->name, le16_to_cpu(pg0->AccessStatus))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Device Info=0x%X\n", + ioc->name, le32_to_cpu(pg0->DeviceInfo))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Flags=0x%X\n", + ioc->name, le16_to_cpu(pg0->Flags))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Physical Port=0x%X\n\n", + ioc->name, pg0->PhysicalPort)); +} + +static void mptsas_print_expander_pg1(MPT_ADAPTER *ioc, SasExpanderPage1_t *pg1) +{ + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "---- SAS EXPANDER PAGE 1 ------------\n", ioc->name)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Physical Port=0x%X\n", + ioc->name, pg1->PhysicalPort)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "PHY Identifier=0x%X\n", + ioc->name, pg1->PhyIdentifier)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Negotiated Link Rate=0x%X\n", + ioc->name, pg1->NegotiatedLinkRate)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Programmed Link Rate=0x%X\n", + ioc->name, pg1->ProgrammedLinkRate)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Hardware Link Rate=0x%X\n", + ioc->name, pg1->HwLinkRate)); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Owner Device Handle=0x%X\n", + ioc->name, le16_to_cpu(pg1->OwnerDevHandle))); + dsasprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Attached Device Handle=0x%X\n\n", ioc->name, + le16_to_cpu(pg1->AttachedDevHandle))); +} + +/* inhibit sas firmware event handling */ +static void +mptsas_fw_event_off(MPT_ADAPTER *ioc) +{ + unsigned long flags; + + spin_lock_irqsave(&ioc->fw_event_lock, flags); + ioc->fw_events_off = 1; + ioc->sas_discovery_quiesce_io = 0; + spin_unlock_irqrestore(&ioc->fw_event_lock, flags); + +} + +/* enable sas firmware event handling */ +static void +mptsas_fw_event_on(MPT_ADAPTER *ioc) +{ + unsigned long flags; + + spin_lock_irqsave(&ioc->fw_event_lock, flags); + ioc->fw_events_off = 0; + spin_unlock_irqrestore(&ioc->fw_event_lock, flags); +} + +/* queue a sas firmware event */ +static void +mptsas_add_fw_event(MPT_ADAPTER *ioc, struct fw_event_work *fw_event, + unsigned long delay) +{ + unsigned long flags; + + spin_lock_irqsave(&ioc->fw_event_lock, flags); + list_add_tail(&fw_event->list, &ioc->fw_event_list); + INIT_DELAYED_WORK(&fw_event->work, mptsas_firmware_event_work); + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT "%s: add (fw_event=0x%p)" + "on cpuid %d\n", ioc->name, __func__, + fw_event, smp_processor_id())); + queue_delayed_work_on(smp_processor_id(), ioc->fw_event_q, + &fw_event->work, delay); + spin_unlock_irqrestore(&ioc->fw_event_lock, flags); +} + +/* requeue a sas firmware event */ +static void +mptsas_requeue_fw_event(MPT_ADAPTER *ioc, struct fw_event_work *fw_event, + unsigned long delay) +{ + unsigned long flags; + spin_lock_irqsave(&ioc->fw_event_lock, flags); + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT "%s: reschedule task " + "(fw_event=0x%p)on cpuid %d\n", ioc->name, __func__, + fw_event, smp_processor_id())); + fw_event->retries++; + queue_delayed_work_on(smp_processor_id(), ioc->fw_event_q, + &fw_event->work, msecs_to_jiffies(delay)); + spin_unlock_irqrestore(&ioc->fw_event_lock, flags); +} + +/* free memory associated to a sas firmware event */ +static void +mptsas_free_fw_event(MPT_ADAPTER *ioc, struct fw_event_work *fw_event) +{ + unsigned long flags; + + spin_lock_irqsave(&ioc->fw_event_lock, flags); + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT "%s: kfree (fw_event=0x%p)\n", + ioc->name, __func__, fw_event)); + list_del(&fw_event->list); + kfree(fw_event); + spin_unlock_irqrestore(&ioc->fw_event_lock, flags); +} + +/* walk the firmware event queue, and either stop or wait for + * outstanding events to complete */ +static void +mptsas_cleanup_fw_event_q(MPT_ADAPTER *ioc) +{ + struct fw_event_work *fw_event, *next; + struct mptsas_target_reset_event *target_reset_list, *n; + MPT_SCSI_HOST *hd = shost_priv(ioc->sh); + + /* flush the target_reset_list */ + if (!list_empty(&hd->target_reset_list)) { + list_for_each_entry_safe(target_reset_list, n, + &hd->target_reset_list, list) { + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: removing target reset for id=%d\n", + ioc->name, __func__, + target_reset_list->sas_event_data.TargetID)); + list_del(&target_reset_list->list); + kfree(target_reset_list); + } + } + + if (list_empty(&ioc->fw_event_list) || + !ioc->fw_event_q || in_interrupt()) + return; + + list_for_each_entry_safe(fw_event, next, &ioc->fw_event_list, list) { + if (cancel_delayed_work(&fw_event->work)) + mptsas_free_fw_event(ioc, fw_event); + } +} + + +static inline MPT_ADAPTER *phy_to_ioc(struct sas_phy *phy) +{ + struct Scsi_Host *shost = dev_to_shost(phy->dev.parent); + return ((MPT_SCSI_HOST *)shost->hostdata)->ioc; +} + +static inline MPT_ADAPTER *rphy_to_ioc(struct sas_rphy *rphy) +{ + struct Scsi_Host *shost = dev_to_shost(rphy->dev.parent->parent); + return ((MPT_SCSI_HOST *)shost->hostdata)->ioc; +} + +/* + * mptsas_find_portinfo_by_handle + * + * This function should be called with the sas_topology_mutex already held + */ +static struct mptsas_portinfo * +mptsas_find_portinfo_by_handle(MPT_ADAPTER *ioc, u16 handle) +{ + struct mptsas_portinfo *port_info, *rc=NULL; + int i; + + list_for_each_entry(port_info, &ioc->sas_topology, list) + for (i = 0; i < port_info->num_phys; i++) + if (port_info->phy_info[i].identify.handle == handle) { + rc = port_info; + goto out; + } + out: + return rc; +} + +/** + * mptsas_find_portinfo_by_sas_address - + * @ioc: Pointer to MPT_ADAPTER structure + * @handle: + * + * This function should be called with the sas_topology_mutex already held + * + **/ +static struct mptsas_portinfo * +mptsas_find_portinfo_by_sas_address(MPT_ADAPTER *ioc, u64 sas_address) +{ + struct mptsas_portinfo *port_info, *rc = NULL; + int i; + + if (sas_address >= ioc->hba_port_sas_addr && + sas_address < (ioc->hba_port_sas_addr + + ioc->hba_port_num_phy)) + return ioc->hba_port_info; + + mutex_lock(&ioc->sas_topology_mutex); + list_for_each_entry(port_info, &ioc->sas_topology, list) + for (i = 0; i < port_info->num_phys; i++) + if (port_info->phy_info[i].identify.sas_address == + sas_address) { + rc = port_info; + goto out; + } + out: + mutex_unlock(&ioc->sas_topology_mutex); + return rc; +} + +/* + * Returns true if there is a scsi end device + */ +static inline int +mptsas_is_end_device(struct mptsas_devinfo * attached) +{ + if ((attached->sas_address) && + (attached->device_info & + MPI_SAS_DEVICE_INFO_END_DEVICE) && + ((attached->device_info & + MPI_SAS_DEVICE_INFO_SSP_TARGET) | + (attached->device_info & + MPI_SAS_DEVICE_INFO_STP_TARGET) | + (attached->device_info & + MPI_SAS_DEVICE_INFO_SATA_DEVICE))) + return 1; + else + return 0; +} + +/* no mutex */ +static void +mptsas_port_delete(MPT_ADAPTER *ioc, struct mptsas_portinfo_details * port_details) +{ + struct mptsas_portinfo *port_info; + struct mptsas_phyinfo *phy_info; + u8 i; + + if (!port_details) + return; + + port_info = port_details->port_info; + phy_info = port_info->phy_info; + + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT "%s: [%p]: num_phys=%02d " + "bitmask=0x%016llX\n", ioc->name, __func__, port_details, + port_details->num_phys, (unsigned long long) + port_details->phy_bitmask)); + + for (i = 0; i < port_info->num_phys; i++, phy_info++) { + if(phy_info->port_details != port_details) + continue; + memset(&phy_info->attached, 0, sizeof(struct mptsas_devinfo)); + mptsas_set_rphy(ioc, phy_info, NULL); + phy_info->port_details = NULL; + } + kfree(port_details); +} + +static inline struct sas_rphy * +mptsas_get_rphy(struct mptsas_phyinfo *phy_info) +{ + if (phy_info->port_details) + return phy_info->port_details->rphy; + else + return NULL; +} + +static inline void +mptsas_set_rphy(MPT_ADAPTER *ioc, struct mptsas_phyinfo *phy_info, struct sas_rphy *rphy) +{ + if (phy_info->port_details) { + phy_info->port_details->rphy = rphy; + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT "sas_rphy_add: rphy=%p\n", + ioc->name, rphy)); + } + + if (rphy) { + dsaswideprintk(ioc, dev_printk(KERN_DEBUG, + &rphy->dev, MYIOC_s_FMT "add:", ioc->name)); + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT "rphy=%p release=%p\n", + ioc->name, rphy, rphy->dev.release)); + } +} + +static inline struct sas_port * +mptsas_get_port(struct mptsas_phyinfo *phy_info) +{ + if (phy_info->port_details) + return phy_info->port_details->port; + else + return NULL; +} + +static inline void +mptsas_set_port(MPT_ADAPTER *ioc, struct mptsas_phyinfo *phy_info, struct sas_port *port) +{ + if (phy_info->port_details) + phy_info->port_details->port = port; + + if (port) { + dsaswideprintk(ioc, dev_printk(KERN_DEBUG, + &port->dev, MYIOC_s_FMT "add:", ioc->name)); + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT "port=%p release=%p\n", + ioc->name, port, port->dev.release)); + } +} + +static inline struct scsi_target * +mptsas_get_starget(struct mptsas_phyinfo *phy_info) +{ + if (phy_info->port_details) + return phy_info->port_details->starget; + else + return NULL; +} + +static inline void +mptsas_set_starget(struct mptsas_phyinfo *phy_info, struct scsi_target * +starget) +{ + if (phy_info->port_details) + phy_info->port_details->starget = starget; +} + +/** + * mptsas_add_device_component - + * @ioc: Pointer to MPT_ADAPTER structure + * @channel: fw mapped id's + * @id: + * @sas_address: + * @device_info: + * + **/ +static void +mptsas_add_device_component(MPT_ADAPTER *ioc, u8 channel, u8 id, + u64 sas_address, u32 device_info, u16 slot, u64 enclosure_logical_id) +{ + struct mptsas_device_info *sas_info, *next; + struct scsi_device *sdev; + struct scsi_target *starget; + struct sas_rphy *rphy; + + /* + * Delete all matching devices out of the list + */ + mutex_lock(&ioc->sas_device_info_mutex); + list_for_each_entry_safe(sas_info, next, &ioc->sas_device_info_list, + list) { + if (!sas_info->is_logical_volume && + (sas_info->sas_address == sas_address || + (sas_info->fw.channel == channel && + sas_info->fw.id == id))) { + list_del(&sas_info->list); + kfree(sas_info); + } + } + + sas_info = kzalloc(sizeof(struct mptsas_device_info), GFP_KERNEL); + if (!sas_info) + goto out; + + /* + * Set Firmware mapping + */ + sas_info->fw.id = id; + sas_info->fw.channel = channel; + + sas_info->sas_address = sas_address; + sas_info->device_info = device_info; + sas_info->slot = slot; + sas_info->enclosure_logical_id = enclosure_logical_id; + INIT_LIST_HEAD(&sas_info->list); + list_add_tail(&sas_info->list, &ioc->sas_device_info_list); + + /* + * Set OS mapping + */ + shost_for_each_device(sdev, ioc->sh) { + starget = scsi_target(sdev); + rphy = dev_to_rphy(starget->dev.parent); + if (rphy->identify.sas_address == sas_address) { + sas_info->os.id = starget->id; + sas_info->os.channel = starget->channel; + } + } + + out: + mutex_unlock(&ioc->sas_device_info_mutex); + return; +} + +/** + * mptsas_add_device_component_by_fw - + * @ioc: Pointer to MPT_ADAPTER structure + * @channel: fw mapped id's + * @id: + * + **/ +static void +mptsas_add_device_component_by_fw(MPT_ADAPTER *ioc, u8 channel, u8 id) +{ + struct mptsas_devinfo sas_device; + struct mptsas_enclosure enclosure_info; + int rc; + + rc = mptsas_sas_device_pg0(ioc, &sas_device, + (MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + (channel << 8) + id); + if (rc) + return; + + memset(&enclosure_info, 0, sizeof(struct mptsas_enclosure)); + mptsas_sas_enclosure_pg0(ioc, &enclosure_info, + (MPI_SAS_ENCLOS_PGAD_FORM_HANDLE << + MPI_SAS_ENCLOS_PGAD_FORM_SHIFT), + sas_device.handle_enclosure); + + mptsas_add_device_component(ioc, sas_device.channel, + sas_device.id, sas_device.sas_address, sas_device.device_info, + sas_device.slot, enclosure_info.enclosure_logical_id); +} + +/** + * mptsas_add_device_component_starget_ir - Handle Integrated RAID, adding each individual device to list + * @ioc: Pointer to MPT_ADAPTER structure + * @channel: fw mapped id's + * @id: + * + **/ +static void +mptsas_add_device_component_starget_ir(MPT_ADAPTER *ioc, + struct scsi_target *starget) +{ + CONFIGPARMS cfg; + ConfigPageHeader_t hdr; + dma_addr_t dma_handle; + pRaidVolumePage0_t buffer = NULL; + int i; + RaidPhysDiskPage0_t phys_disk; + struct mptsas_device_info *sas_info, *next; + + memset(&cfg, 0 , sizeof(CONFIGPARMS)); + memset(&hdr, 0 , sizeof(ConfigPageHeader_t)); + hdr.PageType = MPI_CONFIG_PAGETYPE_RAID_VOLUME; + /* assumption that all volumes on channel = 0 */ + cfg.pageAddr = starget->id; + cfg.cfghdr.hdr = &hdr; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + if (mpt_config(ioc, &cfg) != 0) + goto out; + + if (!hdr.PageLength) + goto out; + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.PageLength * 4, + &dma_handle); + + if (!buffer) + goto out; + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + if (mpt_config(ioc, &cfg) != 0) + goto out; + + if (!buffer->NumPhysDisks) + goto out; + + /* + * Adding entry for hidden components + */ + for (i = 0; i < buffer->NumPhysDisks; i++) { + + if (mpt_raid_phys_disk_pg0(ioc, + buffer->PhysDisk[i].PhysDiskNum, &phys_disk) != 0) + continue; + + mptsas_add_device_component_by_fw(ioc, phys_disk.PhysDiskBus, + phys_disk.PhysDiskID); + + mutex_lock(&ioc->sas_device_info_mutex); + list_for_each_entry(sas_info, &ioc->sas_device_info_list, + list) { + if (!sas_info->is_logical_volume && + (sas_info->fw.channel == phys_disk.PhysDiskBus && + sas_info->fw.id == phys_disk.PhysDiskID)) { + sas_info->is_hidden_raid_component = 1; + sas_info->volume_id = starget->id; + } + } + mutex_unlock(&ioc->sas_device_info_mutex); + + } + + /* + * Delete all matching devices out of the list + */ + mutex_lock(&ioc->sas_device_info_mutex); + list_for_each_entry_safe(sas_info, next, &ioc->sas_device_info_list, + list) { + if (sas_info->is_logical_volume && sas_info->fw.id == + starget->id) { + list_del(&sas_info->list); + kfree(sas_info); + } + } + + sas_info = kzalloc(sizeof(struct mptsas_device_info), GFP_KERNEL); + if (sas_info) { + sas_info->fw.id = starget->id; + sas_info->os.id = starget->id; + sas_info->os.channel = starget->channel; + sas_info->is_logical_volume = 1; + INIT_LIST_HEAD(&sas_info->list); + list_add_tail(&sas_info->list, &ioc->sas_device_info_list); + } + mutex_unlock(&ioc->sas_device_info_mutex); + + out: + if (buffer) + pci_free_consistent(ioc->pcidev, hdr.PageLength * 4, buffer, + dma_handle); +} + +/** + * mptsas_add_device_component_starget - + * @ioc: Pointer to MPT_ADAPTER structure + * @starget: + * + **/ +static void +mptsas_add_device_component_starget(MPT_ADAPTER *ioc, + struct scsi_target *starget) +{ + VirtTarget *vtarget; + struct sas_rphy *rphy; + struct mptsas_phyinfo *phy_info = NULL; + struct mptsas_enclosure enclosure_info; + + rphy = dev_to_rphy(starget->dev.parent); + vtarget = starget->hostdata; + phy_info = mptsas_find_phyinfo_by_sas_address(ioc, + rphy->identify.sas_address); + if (!phy_info) + return; + + memset(&enclosure_info, 0, sizeof(struct mptsas_enclosure)); + mptsas_sas_enclosure_pg0(ioc, &enclosure_info, + (MPI_SAS_ENCLOS_PGAD_FORM_HANDLE << + MPI_SAS_ENCLOS_PGAD_FORM_SHIFT), + phy_info->attached.handle_enclosure); + + mptsas_add_device_component(ioc, phy_info->attached.channel, + phy_info->attached.id, phy_info->attached.sas_address, + phy_info->attached.device_info, + phy_info->attached.slot, enclosure_info.enclosure_logical_id); +} + +/** + * mptsas_del_device_component_by_os - Once a device has been removed, we mark the entry in the list as being cached + * @ioc: Pointer to MPT_ADAPTER structure + * @channel: os mapped id's + * @id: + * + **/ +static void +mptsas_del_device_component_by_os(MPT_ADAPTER *ioc, u8 channel, u8 id) +{ + struct mptsas_device_info *sas_info, *next; + + /* + * Set is_cached flag + */ + list_for_each_entry_safe(sas_info, next, &ioc->sas_device_info_list, + list) { + if (sas_info->os.channel == channel && sas_info->os.id == id) + sas_info->is_cached = 1; + } +} + +/** + * mptsas_del_device_components - Cleaning the list + * @ioc: Pointer to MPT_ADAPTER structure + * + **/ +static void +mptsas_del_device_components(MPT_ADAPTER *ioc) +{ + struct mptsas_device_info *sas_info, *next; + + mutex_lock(&ioc->sas_device_info_mutex); + list_for_each_entry_safe(sas_info, next, &ioc->sas_device_info_list, + list) { + list_del(&sas_info->list); + kfree(sas_info); + } + mutex_unlock(&ioc->sas_device_info_mutex); +} + + +/* + * mptsas_setup_wide_ports + * + * Updates for new and existing narrow/wide port configuration + * in the sas_topology + */ +static void +mptsas_setup_wide_ports(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info) +{ + struct mptsas_portinfo_details * port_details; + struct mptsas_phyinfo *phy_info, *phy_info_cmp; + u64 sas_address; + int i, j; + + mutex_lock(&ioc->sas_topology_mutex); + + phy_info = port_info->phy_info; + for (i = 0 ; i < port_info->num_phys ; i++, phy_info++) { + if (phy_info->attached.handle) + continue; + port_details = phy_info->port_details; + if (!port_details) + continue; + if (port_details->num_phys < 2) + continue; + /* + * Removing a phy from a port, letting the last + * phy be removed by firmware events. + */ + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: [%p]: deleting phy = %d\n", + ioc->name, __func__, port_details, i)); + port_details->num_phys--; + port_details->phy_bitmask &= ~ (1 << phy_info->phy_id); + memset(&phy_info->attached, 0, sizeof(struct mptsas_devinfo)); + if (phy_info->phy) { + devtprintk(ioc, dev_printk(KERN_DEBUG, + &phy_info->phy->dev, MYIOC_s_FMT + "delete phy %d, phy-obj (0x%p)\n", ioc->name, + phy_info->phy_id, phy_info->phy)); + sas_port_delete_phy(port_details->port, phy_info->phy); + } + phy_info->port_details = NULL; + } + + /* + * Populate and refresh the tree + */ + phy_info = port_info->phy_info; + for (i = 0 ; i < port_info->num_phys ; i++, phy_info++) { + sas_address = phy_info->attached.sas_address; + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT "phy_id=%d sas_address=0x%018llX\n", + ioc->name, i, (unsigned long long)sas_address)); + if (!sas_address) + continue; + port_details = phy_info->port_details; + /* + * Forming a port + */ + if (!port_details) { + port_details = kzalloc(sizeof(struct + mptsas_portinfo_details), GFP_KERNEL); + if (!port_details) + goto out; + port_details->num_phys = 1; + port_details->port_info = port_info; + if (phy_info->phy_id < 64 ) + port_details->phy_bitmask |= + (1 << phy_info->phy_id); + phy_info->sas_port_add_phy=1; + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT "\t\tForming port\n\t\t" + "phy_id=%d sas_address=0x%018llX\n", + ioc->name, i, (unsigned long long)sas_address)); + phy_info->port_details = port_details; + } + + if (i == port_info->num_phys - 1) + continue; + phy_info_cmp = &port_info->phy_info[i + 1]; + for (j = i + 1 ; j < port_info->num_phys ; j++, + phy_info_cmp++) { + if (!phy_info_cmp->attached.sas_address) + continue; + if (sas_address != phy_info_cmp->attached.sas_address) + continue; + if (phy_info_cmp->port_details == port_details ) + continue; + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "\t\tphy_id=%d sas_address=0x%018llX\n", + ioc->name, j, (unsigned long long) + phy_info_cmp->attached.sas_address)); + if (phy_info_cmp->port_details) { + port_details->rphy = + mptsas_get_rphy(phy_info_cmp); + port_details->port = + mptsas_get_port(phy_info_cmp); + port_details->starget = + mptsas_get_starget(phy_info_cmp); + port_details->num_phys = + phy_info_cmp->port_details->num_phys; + if (!phy_info_cmp->port_details->num_phys) + kfree(phy_info_cmp->port_details); + } else + phy_info_cmp->sas_port_add_phy=1; + /* + * Adding a phy to a port + */ + phy_info_cmp->port_details = port_details; + if (phy_info_cmp->phy_id < 64 ) + port_details->phy_bitmask |= + (1 << phy_info_cmp->phy_id); + port_details->num_phys++; + } + } + + out: + + for (i = 0; i < port_info->num_phys; i++) { + port_details = port_info->phy_info[i].port_details; + if (!port_details) + continue; + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: [%p]: phy_id=%02d num_phys=%02d " + "bitmask=0x%016llX\n", ioc->name, __func__, + port_details, i, port_details->num_phys, + (unsigned long long)port_details->phy_bitmask)); + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT "\t\tport = %p rphy=%p\n", + ioc->name, port_details->port, port_details->rphy)); + } + dsaswideprintk(ioc, printk("\n")); + mutex_unlock(&ioc->sas_topology_mutex); +} + +/** + * csmisas_find_vtarget + * + * @ioc + * @volume_id + * @volume_bus + * + **/ +static VirtTarget * +mptsas_find_vtarget(MPT_ADAPTER *ioc, u8 channel, u8 id) +{ + struct scsi_device *sdev; + VirtDevice *vdevice; + VirtTarget *vtarget = NULL; + + shost_for_each_device(sdev, ioc->sh) { + vdevice = sdev->hostdata; + if ((vdevice == NULL) || + (vdevice->vtarget == NULL)) + continue; + if ((vdevice->vtarget->tflags & + MPT_TARGET_FLAGS_RAID_COMPONENT || + vdevice->vtarget->raidVolume)) + continue; + if (vdevice->vtarget->id == id && + vdevice->vtarget->channel == channel) + vtarget = vdevice->vtarget; + } + return vtarget; +} + +static void +mptsas_queue_device_delete(MPT_ADAPTER *ioc, + MpiEventDataSasDeviceStatusChange_t *sas_event_data) +{ + struct fw_event_work *fw_event; + + fw_event = kzalloc(sizeof(*fw_event) + + sizeof(MpiEventDataSasDeviceStatusChange_t), + GFP_ATOMIC); + if (!fw_event) { + printk(MYIOC_s_WARN_FMT "%s: failed at (line=%d)\n", + ioc->name, __func__, __LINE__); + return; + } + memcpy(fw_event->event_data, sas_event_data, + sizeof(MpiEventDataSasDeviceStatusChange_t)); + fw_event->event = MPI_EVENT_SAS_DEVICE_STATUS_CHANGE; + fw_event->ioc = ioc; + mptsas_add_fw_event(ioc, fw_event, msecs_to_jiffies(1)); +} + +static void +mptsas_queue_rescan(MPT_ADAPTER *ioc) +{ + struct fw_event_work *fw_event; + + fw_event = kzalloc(sizeof(*fw_event), GFP_ATOMIC); + if (!fw_event) { + printk(MYIOC_s_WARN_FMT "%s: failed at (line=%d)\n", + ioc->name, __func__, __LINE__); + return; + } + fw_event->event = -1; + fw_event->ioc = ioc; + mptsas_add_fw_event(ioc, fw_event, msecs_to_jiffies(1)); +} + + +/** + * mptsas_target_reset + * + * Issues TARGET_RESET to end device using handshaking method + * + * @ioc + * @channel + * @id + * + * Returns (1) success + * (0) failure + * + **/ +static int +mptsas_target_reset(MPT_ADAPTER *ioc, u8 channel, u8 id) +{ + MPT_FRAME_HDR *mf; + SCSITaskMgmt_t *pScsiTm; + if (mpt_set_taskmgmt_in_progress_flag(ioc) != 0) + return 0; + + + mf = mpt_get_msg_frame(mptsasDeviceResetCtx, ioc); + if (mf == NULL) { + dfailprintk(ioc, printk(MYIOC_s_WARN_FMT + "%s, no msg frames @%d!!\n", ioc->name, + __func__, __LINE__)); + goto out_fail; + } + + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT "TaskMgmt request (mf=%p)\n", + ioc->name, mf)); + + /* Format the Request + */ + pScsiTm = (SCSITaskMgmt_t *) mf; + memset (pScsiTm, 0, sizeof(SCSITaskMgmt_t)); + pScsiTm->TargetID = id; + pScsiTm->Bus = channel; + pScsiTm->Function = MPI_FUNCTION_SCSI_TASK_MGMT; + pScsiTm->TaskType = MPI_SCSITASKMGMT_TASKTYPE_TARGET_RESET; + pScsiTm->MsgFlags = MPI_SCSITASKMGMT_MSGFLAGS_LIPRESET_RESET_OPTION; + + DBG_DUMP_TM_REQUEST_FRAME(ioc, (u32 *)mf); + + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "TaskMgmt type=%d (sas device delete) fw_channel = %d fw_id = %d)\n", + ioc->name, MPI_SCSITASKMGMT_TASKTYPE_TARGET_RESET, channel, id)); + + mpt_put_msg_frame_hi_pri(mptsasDeviceResetCtx, ioc, mf); + + return 1; + + out_fail: + + mpt_clear_taskmgmt_in_progress_flag(ioc); + return 0; +} + +static void +mptsas_block_io_sdev(struct scsi_device *sdev, void *data) +{ + scsi_device_set_state(sdev, SDEV_BLOCK); +} + +static void +mptsas_block_io_starget(struct scsi_target *starget) +{ + if (starget) + starget_for_each_device(starget, NULL, mptsas_block_io_sdev); +} + +/** + * mptsas_target_reset_queue + * + * Receive request for TARGET_RESET after receiving an firmware + * event NOT_RESPONDING_EVENT, then put command in link list + * and queue if task_queue already in use. + * + * @ioc + * @sas_event_data + * + **/ +static void +mptsas_target_reset_queue(MPT_ADAPTER *ioc, + EVENT_DATA_SAS_DEVICE_STATUS_CHANGE *sas_event_data) +{ + MPT_SCSI_HOST *hd = shost_priv(ioc->sh); + VirtTarget *vtarget = NULL; + struct mptsas_target_reset_event *target_reset_list; + u8 id, channel; + + id = sas_event_data->TargetID; + channel = sas_event_data->Bus; + + vtarget = mptsas_find_vtarget(ioc, channel, id); + if (vtarget) { + mptsas_block_io_starget(vtarget->starget); + vtarget->deleted = 1; /* block IO */ + } + + target_reset_list = kzalloc(sizeof(struct mptsas_target_reset_event), + GFP_ATOMIC); + if (!target_reset_list) { + dfailprintk(ioc, printk(MYIOC_s_WARN_FMT + "%s, failed to allocate mem @%d..!!\n", + ioc->name, __func__, __LINE__)); + return; + } + + memcpy(&target_reset_list->sas_event_data, sas_event_data, + sizeof(*sas_event_data)); + list_add_tail(&target_reset_list->list, &hd->target_reset_list); + + target_reset_list->time_count = jiffies; + + if (mptsas_target_reset(ioc, channel, id)) { + target_reset_list->target_reset_issued = 1; + } +} + +/** + * mptsas_schedule_target_reset- send pending target reset + * @iocp: per adapter object + * + * This function will delete scheduled target reset from the list and + * try to send next target reset. This will be called from completion + * context of any Task management command. + */ + +void +mptsas_schedule_target_reset(void *iocp) +{ + MPT_ADAPTER *ioc = (MPT_ADAPTER *)(iocp); + MPT_SCSI_HOST *hd = shost_priv(ioc->sh); + struct list_head *head = &hd->target_reset_list; + struct mptsas_target_reset_event *target_reset_list; + u8 id, channel; + /* + * issue target reset to next device in the queue + */ + + head = &hd->target_reset_list; + if (list_empty(head)) + return; + + target_reset_list = list_entry(head->next, + struct mptsas_target_reset_event, list); + + id = target_reset_list->sas_event_data.TargetID; + channel = target_reset_list->sas_event_data.Bus; + target_reset_list->time_count = jiffies; + + if (mptsas_target_reset(ioc, channel, id)) + target_reset_list->target_reset_issued = 1; + return; +} + + +/** + * mptsas_taskmgmt_complete - complete SAS task management function + * @ioc: Pointer to MPT_ADAPTER structure + * + * Completion for TARGET_RESET after NOT_RESPONDING_EVENT, enable work + * queue to finish off removing device from upper layers. then send next + * TARGET_RESET in the queue. + **/ +static int +mptsas_taskmgmt_complete(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *mr) +{ + MPT_SCSI_HOST *hd = shost_priv(ioc->sh); + struct list_head *head = &hd->target_reset_list; + u8 id, channel; + struct mptsas_target_reset_event *target_reset_list; + SCSITaskMgmtReply_t *pScsiTmReply; + + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT "TaskMgmt completed: " + "(mf = %p, mr = %p)\n", ioc->name, mf, mr)); + + pScsiTmReply = (SCSITaskMgmtReply_t *)mr; + if (!pScsiTmReply) + return 0; + + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "\tTaskMgmt completed: fw_channel = %d, fw_id = %d,\n" + "\ttask_type = 0x%02X, iocstatus = 0x%04X " + "loginfo = 0x%08X,\n\tresponse_code = 0x%02X, " + "term_cmnds = %d\n", ioc->name, + pScsiTmReply->Bus, pScsiTmReply->TargetID, + pScsiTmReply->TaskType, + le16_to_cpu(pScsiTmReply->IOCStatus), + le32_to_cpu(pScsiTmReply->IOCLogInfo), + pScsiTmReply->ResponseCode, + le32_to_cpu(pScsiTmReply->TerminationCount))); + + if (pScsiTmReply->ResponseCode) + mptscsih_taskmgmt_response_code(ioc, + pScsiTmReply->ResponseCode); + + if (pScsiTmReply->TaskType == + MPI_SCSITASKMGMT_TASKTYPE_QUERY_TASK || pScsiTmReply->TaskType == + MPI_SCSITASKMGMT_TASKTYPE_ABRT_TASK_SET) { + ioc->taskmgmt_cmds.status |= MPT_MGMT_STATUS_COMMAND_GOOD; + ioc->taskmgmt_cmds.status |= MPT_MGMT_STATUS_RF_VALID; + memcpy(ioc->taskmgmt_cmds.reply, mr, + min(MPT_DEFAULT_FRAME_SIZE, 4 * mr->u.reply.MsgLength)); + if (ioc->taskmgmt_cmds.status & MPT_MGMT_STATUS_PENDING) { + ioc->taskmgmt_cmds.status &= ~MPT_MGMT_STATUS_PENDING; + complete(&ioc->taskmgmt_cmds.done); + return 1; + } + return 0; + } + + mpt_clear_taskmgmt_in_progress_flag(ioc); + + if (list_empty(head)) + return 1; + + target_reset_list = list_entry(head->next, + struct mptsas_target_reset_event, list); + + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "TaskMgmt: completed (%d seconds)\n", + ioc->name, jiffies_to_msecs(jiffies - + target_reset_list->time_count)/1000)); + + id = pScsiTmReply->TargetID; + channel = pScsiTmReply->Bus; + target_reset_list->time_count = jiffies; + + /* + * retry target reset + */ + if (!target_reset_list->target_reset_issued) { + if (mptsas_target_reset(ioc, channel, id)) + target_reset_list->target_reset_issued = 1; + return 1; + } + + /* + * enable work queue to remove device from upper layers + */ + list_del(&target_reset_list->list); + if (!ioc->fw_events_off) + mptsas_queue_device_delete(ioc, + &target_reset_list->sas_event_data); + + + ioc->schedule_target_reset(ioc); + + return 1; +} + +/** + * mptscsih_ioc_reset + * + * @ioc + * @reset_phase + * + **/ +static int +mptsas_ioc_reset(MPT_ADAPTER *ioc, int reset_phase) +{ + MPT_SCSI_HOST *hd; + int rc; + + rc = mptscsih_ioc_reset(ioc, reset_phase); + if ((ioc->bus_type != SAS) || (!rc)) + return rc; + + hd = shost_priv(ioc->sh); + if (!hd->ioc) + goto out; + + switch (reset_phase) { + case MPT_IOC_SETUP_RESET: + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: MPT_IOC_SETUP_RESET\n", ioc->name, __func__)); + mptsas_fw_event_off(ioc); + break; + case MPT_IOC_PRE_RESET: + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: MPT_IOC_PRE_RESET\n", ioc->name, __func__)); + break; + case MPT_IOC_POST_RESET: + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: MPT_IOC_POST_RESET\n", ioc->name, __func__)); + if (ioc->sas_mgmt.status & MPT_MGMT_STATUS_PENDING) { + ioc->sas_mgmt.status |= MPT_MGMT_STATUS_DID_IOCRESET; + complete(&ioc->sas_mgmt.done); + } + mptsas_cleanup_fw_event_q(ioc); + mptsas_queue_rescan(ioc); + break; + default: + break; + } + + out: + return rc; +} + + +/** + * enum device_state - + * @DEVICE_RETRY: need to retry the TUR + * @DEVICE_ERROR: TUR return error, don't add device + * @DEVICE_READY: device can be added + * + */ +enum device_state{ + DEVICE_RETRY, + DEVICE_ERROR, + DEVICE_READY, +}; + +static int +mptsas_sas_enclosure_pg0(MPT_ADAPTER *ioc, struct mptsas_enclosure *enclosure, + u32 form, u32 form_specific) +{ + ConfigExtendedPageHeader_t hdr; + CONFIGPARMS cfg; + SasEnclosurePage0_t *buffer; + dma_addr_t dma_handle; + int error; + __le64 le_identifier; + + memset(&hdr, 0, sizeof(hdr)); + hdr.PageVersion = MPI_SASENCLOSURE0_PAGEVERSION; + hdr.PageNumber = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_EXTENDED; + hdr.ExtPageType = MPI_CONFIG_EXTPAGETYPE_ENCLOSURE; + + cfg.cfghdr.ehdr = &hdr; + cfg.physAddr = -1; + cfg.pageAddr = form + form_specific; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; /* read */ + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + error = mpt_config(ioc, &cfg); + if (error) + goto out; + if (!hdr.ExtPageLength) { + error = -ENXIO; + goto out; + } + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + &dma_handle); + if (!buffer) { + error = -ENOMEM; + goto out; + } + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + error = mpt_config(ioc, &cfg); + if (error) + goto out_free_consistent; + + /* save config data */ + memcpy(&le_identifier, &buffer->EnclosureLogicalID, sizeof(__le64)); + enclosure->enclosure_logical_id = le64_to_cpu(le_identifier); + enclosure->enclosure_handle = le16_to_cpu(buffer->EnclosureHandle); + enclosure->flags = le16_to_cpu(buffer->Flags); + enclosure->num_slot = le16_to_cpu(buffer->NumSlots); + enclosure->start_slot = le16_to_cpu(buffer->StartSlot); + enclosure->start_id = buffer->StartTargetID; + enclosure->start_channel = buffer->StartBus; + enclosure->sep_id = buffer->SEPTargetID; + enclosure->sep_channel = buffer->SEPBus; + + out_free_consistent: + pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + buffer, dma_handle); + out: + return error; +} + +/** + * mptsas_add_end_device - report a new end device to sas transport layer + * @ioc: Pointer to MPT_ADAPTER structure + * @phy_info: describes attached device + * + * return (0) success (1) failure + * + **/ +static int +mptsas_add_end_device(MPT_ADAPTER *ioc, struct mptsas_phyinfo *phy_info) +{ + struct sas_rphy *rphy; + struct sas_port *port; + struct sas_identify identify; + char *ds = NULL; + u8 fw_id; + + if (!phy_info) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: exit at line=%d\n", ioc->name, + __func__, __LINE__)); + return 1; + } + + fw_id = phy_info->attached.id; + + if (mptsas_get_rphy(phy_info)) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, fw_id, __LINE__)); + return 2; + } + + port = mptsas_get_port(phy_info); + if (!port) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, fw_id, __LINE__)); + return 3; + } + + if (phy_info->attached.device_info & + MPI_SAS_DEVICE_INFO_SSP_TARGET) + ds = "ssp"; + if (phy_info->attached.device_info & + MPI_SAS_DEVICE_INFO_STP_TARGET) + ds = "stp"; + if (phy_info->attached.device_info & + MPI_SAS_DEVICE_INFO_SATA_DEVICE) + ds = "sata"; + + printk(MYIOC_s_INFO_FMT "attaching %s device: fw_channel %d, fw_id %d," + " phy %d, sas_addr 0x%llx\n", ioc->name, ds, + phy_info->attached.channel, phy_info->attached.id, + phy_info->attached.phy_id, (unsigned long long) + phy_info->attached.sas_address); + + mptsas_parse_device_info(&identify, &phy_info->attached); + rphy = sas_end_device_alloc(port); + if (!rphy) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, fw_id, __LINE__)); + return 5; /* non-fatal: an rphy can be added later */ + } + + rphy->identify = identify; + if (sas_rphy_add(rphy)) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, fw_id, __LINE__)); + sas_rphy_free(rphy); + return 6; + } + mptsas_set_rphy(ioc, phy_info, rphy); + return 0; +} + +/** + * mptsas_del_end_device - report a deleted end device to sas transport layer + * @ioc: Pointer to MPT_ADAPTER structure + * @phy_info: describes attached device + * + **/ +static void +mptsas_del_end_device(MPT_ADAPTER *ioc, struct mptsas_phyinfo *phy_info) +{ + struct sas_rphy *rphy; + struct sas_port *port; + struct mptsas_portinfo *port_info; + struct mptsas_phyinfo *phy_info_parent; + int i; + char *ds = NULL; + u8 fw_id; + u64 sas_address; + + if (!phy_info) + return; + + fw_id = phy_info->attached.id; + sas_address = phy_info->attached.sas_address; + + if (!phy_info->port_details) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, fw_id, __LINE__)); + return; + } + rphy = mptsas_get_rphy(phy_info); + if (!rphy) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, fw_id, __LINE__)); + return; + } + + if (phy_info->attached.device_info & MPI_SAS_DEVICE_INFO_SSP_INITIATOR + || phy_info->attached.device_info + & MPI_SAS_DEVICE_INFO_SMP_INITIATOR + || phy_info->attached.device_info + & MPI_SAS_DEVICE_INFO_STP_INITIATOR) + ds = "initiator"; + if (phy_info->attached.device_info & + MPI_SAS_DEVICE_INFO_SSP_TARGET) + ds = "ssp"; + if (phy_info->attached.device_info & + MPI_SAS_DEVICE_INFO_STP_TARGET) + ds = "stp"; + if (phy_info->attached.device_info & + MPI_SAS_DEVICE_INFO_SATA_DEVICE) + ds = "sata"; + + dev_printk(KERN_DEBUG, &rphy->dev, MYIOC_s_FMT + "removing %s device: fw_channel %d, fw_id %d, phy %d," + "sas_addr 0x%llx\n", ioc->name, ds, phy_info->attached.channel, + phy_info->attached.id, phy_info->attached.phy_id, + (unsigned long long) sas_address); + + port = mptsas_get_port(phy_info); + if (!port) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, fw_id, __LINE__)); + return; + } + port_info = phy_info->portinfo; + phy_info_parent = port_info->phy_info; + for (i = 0; i < port_info->num_phys; i++, phy_info_parent++) { + if (!phy_info_parent->phy) + continue; + if (phy_info_parent->attached.sas_address != + sas_address) + continue; + dev_printk(KERN_DEBUG, &phy_info_parent->phy->dev, + MYIOC_s_FMT "delete phy %d, phy-obj (0x%p)\n", + ioc->name, phy_info_parent->phy_id, + phy_info_parent->phy); + sas_port_delete_phy(port, phy_info_parent->phy); + } + + dev_printk(KERN_DEBUG, &port->dev, MYIOC_s_FMT + "delete port %d, sas_addr (0x%llx)\n", ioc->name, + port->port_identifier, (unsigned long long)sas_address); + sas_port_delete(port); + mptsas_set_port(ioc, phy_info, NULL); + mptsas_port_delete(ioc, phy_info->port_details); +} + +static struct mptsas_phyinfo * +mptsas_refreshing_device_handles(MPT_ADAPTER *ioc, + struct mptsas_devinfo *sas_device) +{ + struct mptsas_phyinfo *phy_info; + struct mptsas_portinfo *port_info; + int i; + + phy_info = mptsas_find_phyinfo_by_sas_address(ioc, + sas_device->sas_address); + if (!phy_info) + goto out; + port_info = phy_info->portinfo; + if (!port_info) + goto out; + mutex_lock(&ioc->sas_topology_mutex); + for (i = 0; i < port_info->num_phys; i++) { + if (port_info->phy_info[i].attached.sas_address != + sas_device->sas_address) + continue; + port_info->phy_info[i].attached.channel = sas_device->channel; + port_info->phy_info[i].attached.id = sas_device->id; + port_info->phy_info[i].attached.sas_address = + sas_device->sas_address; + port_info->phy_info[i].attached.handle = sas_device->handle; + port_info->phy_info[i].attached.handle_parent = + sas_device->handle_parent; + port_info->phy_info[i].attached.handle_enclosure = + sas_device->handle_enclosure; + } + mutex_unlock(&ioc->sas_topology_mutex); + out: + return phy_info; +} + +/** + * mptsas_firmware_event_work - work thread for processing fw events + * @work: work queue payload containing info describing the event + * Context: user + * + */ +static void +mptsas_firmware_event_work(struct work_struct *work) +{ + struct fw_event_work *fw_event = + container_of(work, struct fw_event_work, work.work); + MPT_ADAPTER *ioc = fw_event->ioc; + + /* special rescan topology handling */ + if (fw_event->event == -1) { + if (ioc->in_rescan) { + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: rescan ignored as it is in progress\n", + ioc->name, __func__)); + return; + } + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT "%s: rescan after " + "reset\n", ioc->name, __func__)); + ioc->in_rescan = 1; + mptsas_not_responding_devices(ioc); + mptsas_scan_sas_topology(ioc); + ioc->in_rescan = 0; + mptsas_free_fw_event(ioc, fw_event); + mptsas_fw_event_on(ioc); + return; + } + + /* events handling turned off during host reset */ + if (ioc->fw_events_off) { + mptsas_free_fw_event(ioc, fw_event); + return; + } + + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT "%s: fw_event=(0x%p), " + "event = (0x%02x)\n", ioc->name, __func__, fw_event, + (fw_event->event & 0xFF))); + + switch (fw_event->event) { + case MPI_EVENT_SAS_DEVICE_STATUS_CHANGE: + mptsas_send_sas_event(fw_event); + break; + case MPI_EVENT_INTEGRATED_RAID: + mptsas_send_raid_event(fw_event); + break; + case MPI_EVENT_IR2: + mptsas_send_ir2_event(fw_event); + break; + case MPI_EVENT_PERSISTENT_TABLE_FULL: + mptbase_sas_persist_operation(ioc, + MPI_SAS_OP_CLEAR_NOT_PRESENT); + mptsas_free_fw_event(ioc, fw_event); + break; + case MPI_EVENT_SAS_BROADCAST_PRIMITIVE: + mptsas_broadcast_primative_work(fw_event); + break; + case MPI_EVENT_SAS_EXPANDER_STATUS_CHANGE: + mptsas_send_expander_event(fw_event); + break; + case MPI_EVENT_SAS_PHY_LINK_STATUS: + mptsas_send_link_status_event(fw_event); + break; + case MPI_EVENT_QUEUE_FULL: + mptsas_handle_queue_full_event(fw_event); + break; + } +} + + + +static int +mptsas_slave_configure(struct scsi_device *sdev) +{ + struct Scsi_Host *host = sdev->host; + MPT_SCSI_HOST *hd = shost_priv(host); + MPT_ADAPTER *ioc = hd->ioc; + VirtDevice *vdevice = sdev->hostdata; + + if (vdevice->vtarget->deleted) { + sdev_printk(KERN_INFO, sdev, "clearing deleted flag\n"); + vdevice->vtarget->deleted = 0; + } + + /* + * RAID volumes placed beyond the last expected port. + * Ignore sending sas mode pages in that case.. + */ + if (sdev->channel == MPTSAS_RAID_CHANNEL) { + mptsas_add_device_component_starget_ir(ioc, scsi_target(sdev)); + goto out; + } + + sas_read_port_mode_page(sdev); + + mptsas_add_device_component_starget(ioc, scsi_target(sdev)); + + out: + return mptscsih_slave_configure(sdev); +} + +static int +mptsas_target_alloc(struct scsi_target *starget) +{ + struct Scsi_Host *host = dev_to_shost(&starget->dev); + MPT_SCSI_HOST *hd = shost_priv(host); + VirtTarget *vtarget; + u8 id, channel; + struct sas_rphy *rphy; + struct mptsas_portinfo *p; + int i; + MPT_ADAPTER *ioc = hd->ioc; + + vtarget = kzalloc(sizeof(VirtTarget), GFP_KERNEL); + if (!vtarget) + return -ENOMEM; + + vtarget->starget = starget; + vtarget->ioc_id = ioc->id; + vtarget->tflags = MPT_TARGET_FLAGS_Q_YES; + id = starget->id; + channel = 0; + + /* + * RAID volumes placed beyond the last expected port. + */ + if (starget->channel == MPTSAS_RAID_CHANNEL) { + if (!ioc->raid_data.pIocPg2) { + kfree(vtarget); + return -ENXIO; + } + for (i = 0; i < ioc->raid_data.pIocPg2->NumActiveVolumes; i++) { + if (id == ioc->raid_data.pIocPg2-> + RaidVolume[i].VolumeID) { + channel = ioc->raid_data.pIocPg2-> + RaidVolume[i].VolumeBus; + } + } + vtarget->raidVolume = 1; + goto out; + } + + rphy = dev_to_rphy(starget->dev.parent); + mutex_lock(&ioc->sas_topology_mutex); + list_for_each_entry(p, &ioc->sas_topology, list) { + for (i = 0; i < p->num_phys; i++) { + if (p->phy_info[i].attached.sas_address != + rphy->identify.sas_address) + continue; + id = p->phy_info[i].attached.id; + channel = p->phy_info[i].attached.channel; + mptsas_set_starget(&p->phy_info[i], starget); + + /* + * Exposing hidden raid components + */ + if (mptscsih_is_phys_disk(ioc, channel, id)) { + id = mptscsih_raid_id_to_num(ioc, + channel, id); + vtarget->tflags |= + MPT_TARGET_FLAGS_RAID_COMPONENT; + p->phy_info[i].attached.phys_disk_num = id; + } + mutex_unlock(&ioc->sas_topology_mutex); + goto out; + } + } + mutex_unlock(&ioc->sas_topology_mutex); + + kfree(vtarget); + return -ENXIO; + + out: + vtarget->id = id; + vtarget->channel = channel; + starget->hostdata = vtarget; + return 0; +} + +static void +mptsas_target_destroy(struct scsi_target *starget) +{ + struct Scsi_Host *host = dev_to_shost(&starget->dev); + MPT_SCSI_HOST *hd = shost_priv(host); + struct sas_rphy *rphy; + struct mptsas_portinfo *p; + int i; + MPT_ADAPTER *ioc = hd->ioc; + VirtTarget *vtarget; + + if (!starget->hostdata) + return; + + vtarget = starget->hostdata; + + mptsas_del_device_component_by_os(ioc, starget->channel, + starget->id); + + + if (starget->channel == MPTSAS_RAID_CHANNEL) + goto out; + + rphy = dev_to_rphy(starget->dev.parent); + list_for_each_entry(p, &ioc->sas_topology, list) { + for (i = 0; i < p->num_phys; i++) { + if (p->phy_info[i].attached.sas_address != + rphy->identify.sas_address) + continue; + + starget_printk(KERN_INFO, starget, MYIOC_s_FMT + "delete device: fw_channel %d, fw_id %d, phy %d, " + "sas_addr 0x%llx\n", ioc->name, + p->phy_info[i].attached.channel, + p->phy_info[i].attached.id, + p->phy_info[i].attached.phy_id, (unsigned long long) + p->phy_info[i].attached.sas_address); + + mptsas_set_starget(&p->phy_info[i], NULL); + } + } + + out: + vtarget->starget = NULL; + kfree(starget->hostdata); + starget->hostdata = NULL; +} + + +static int +mptsas_slave_alloc(struct scsi_device *sdev) +{ + struct Scsi_Host *host = sdev->host; + MPT_SCSI_HOST *hd = shost_priv(host); + struct sas_rphy *rphy; + struct mptsas_portinfo *p; + VirtDevice *vdevice; + struct scsi_target *starget; + int i; + MPT_ADAPTER *ioc = hd->ioc; + + vdevice = kzalloc(sizeof(VirtDevice), GFP_KERNEL); + if (!vdevice) { + printk(MYIOC_s_ERR_FMT "slave_alloc kzalloc(%zd) FAILED!\n", + ioc->name, sizeof(VirtDevice)); + return -ENOMEM; + } + starget = scsi_target(sdev); + vdevice->vtarget = starget->hostdata; + + if (sdev->channel == MPTSAS_RAID_CHANNEL) + goto out; + + rphy = dev_to_rphy(sdev->sdev_target->dev.parent); + mutex_lock(&ioc->sas_topology_mutex); + list_for_each_entry(p, &ioc->sas_topology, list) { + for (i = 0; i < p->num_phys; i++) { + if (p->phy_info[i].attached.sas_address != + rphy->identify.sas_address) + continue; + vdevice->lun = sdev->lun; + /* + * Exposing hidden raid components + */ + if (mptscsih_is_phys_disk(ioc, + p->phy_info[i].attached.channel, + p->phy_info[i].attached.id)) + sdev->no_uld_attach = 1; + mutex_unlock(&ioc->sas_topology_mutex); + goto out; + } + } + mutex_unlock(&ioc->sas_topology_mutex); + + kfree(vdevice); + return -ENXIO; + + out: + vdevice->vtarget->num_luns++; + sdev->hostdata = vdevice; + return 0; +} + +static int +mptsas_qcmd(struct Scsi_Host *shost, struct scsi_cmnd *SCpnt) +{ + MPT_SCSI_HOST *hd; + MPT_ADAPTER *ioc; + VirtDevice *vdevice = SCpnt->device->hostdata; + + if (!vdevice || !vdevice->vtarget || vdevice->vtarget->deleted) { + SCpnt->result = DID_NO_CONNECT << 16; + SCpnt->scsi_done(SCpnt); + return 0; + } + + hd = shost_priv(shost); + ioc = hd->ioc; + + if (ioc->sas_discovery_quiesce_io) + return SCSI_MLQUEUE_HOST_BUSY; + + if (ioc->debug_level & MPT_DEBUG_SCSI) + scsi_print_command(SCpnt); + + return mptscsih_qcmd(SCpnt); +} + +/** + * mptsas_mptsas_eh_timed_out - resets the scsi_cmnd timeout + * if the device under question is currently in the + * device removal delay. + * @sc: scsi command that the midlayer is about to time out + * + **/ +static enum blk_eh_timer_return mptsas_eh_timed_out(struct scsi_cmnd *sc) +{ + MPT_SCSI_HOST *hd; + MPT_ADAPTER *ioc; + VirtDevice *vdevice; + enum blk_eh_timer_return rc = BLK_EH_NOT_HANDLED; + + hd = shost_priv(sc->device->host); + if (hd == NULL) { + printk(KERN_ERR MYNAM ": %s: Can't locate host! (sc=%p)\n", + __func__, sc); + goto done; + } + + ioc = hd->ioc; + if (ioc->bus_type != SAS) { + printk(KERN_ERR MYNAM ": %s: Wrong bus type (sc=%p)\n", + __func__, sc); + goto done; + } + + /* In case if IOC is in reset from internal context. + * Do not execute EEH for the same IOC. SML should to reset timer. + */ + if (ioc->ioc_reset_in_progress) { + dtmprintk(ioc, printk(MYIOC_s_WARN_FMT ": %s: ioc is in reset," + "SML need to reset the timer (sc=%p)\n", + ioc->name, __func__, sc)); + rc = BLK_EH_RESET_TIMER; + } + vdevice = sc->device->hostdata; + if (vdevice && vdevice->vtarget && (vdevice->vtarget->inDMD + || vdevice->vtarget->deleted)) { + dtmprintk(ioc, printk(MYIOC_s_WARN_FMT ": %s: target removed " + "or in device removal delay (sc=%p)\n", + ioc->name, __func__, sc)); + rc = BLK_EH_RESET_TIMER; + goto done; + } + +done: + return rc; +} + + +static struct scsi_host_template mptsas_driver_template = { + .module = THIS_MODULE, + .proc_name = "mptsas", + .show_info = mptscsih_show_info, + .name = "MPT SAS Host", + .info = mptscsih_info, + .queuecommand = mptsas_qcmd, + .target_alloc = mptsas_target_alloc, + .slave_alloc = mptsas_slave_alloc, + .slave_configure = mptsas_slave_configure, + .target_destroy = mptsas_target_destroy, + .slave_destroy = mptscsih_slave_destroy, + .change_queue_depth = mptscsih_change_queue_depth, + .eh_abort_handler = mptscsih_abort, + .eh_device_reset_handler = mptscsih_dev_reset, + .eh_host_reset_handler = mptscsih_host_reset, + .bios_param = mptscsih_bios_param, + .can_queue = MPT_SAS_CAN_QUEUE, + .this_id = -1, + .sg_tablesize = MPT_SCSI_SG_DEPTH, + .max_sectors = 8192, + .cmd_per_lun = 7, + .use_clustering = ENABLE_CLUSTERING, + .shost_attrs = mptscsih_host_attrs, + .use_blk_tags = 1, +}; + +static int mptsas_get_linkerrors(struct sas_phy *phy) +{ + MPT_ADAPTER *ioc = phy_to_ioc(phy); + ConfigExtendedPageHeader_t hdr; + CONFIGPARMS cfg; + SasPhyPage1_t *buffer; + dma_addr_t dma_handle; + int error; + + /* FIXME: only have link errors on local phys */ + if (!scsi_is_sas_phy_local(phy)) + return -EINVAL; + + hdr.PageVersion = MPI_SASPHY1_PAGEVERSION; + hdr.ExtPageLength = 0; + hdr.PageNumber = 1 /* page number 1*/; + hdr.Reserved1 = 0; + hdr.Reserved2 = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_EXTENDED; + hdr.ExtPageType = MPI_CONFIG_EXTPAGETYPE_SAS_PHY; + + cfg.cfghdr.ehdr = &hdr; + cfg.physAddr = -1; + cfg.pageAddr = phy->identify.phy_identifier; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; /* read */ + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + error = mpt_config(ioc, &cfg); + if (error) + return error; + if (!hdr.ExtPageLength) + return -ENXIO; + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + &dma_handle); + if (!buffer) + return -ENOMEM; + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + error = mpt_config(ioc, &cfg); + if (error) + goto out_free_consistent; + + mptsas_print_phy_pg1(ioc, buffer); + + phy->invalid_dword_count = le32_to_cpu(buffer->InvalidDwordCount); + phy->running_disparity_error_count = + le32_to_cpu(buffer->RunningDisparityErrorCount); + phy->loss_of_dword_sync_count = + le32_to_cpu(buffer->LossDwordSynchCount); + phy->phy_reset_problem_count = + le32_to_cpu(buffer->PhyResetProblemCount); + + out_free_consistent: + pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + buffer, dma_handle); + return error; +} + +static int mptsas_mgmt_done(MPT_ADAPTER *ioc, MPT_FRAME_HDR *req, + MPT_FRAME_HDR *reply) +{ + ioc->sas_mgmt.status |= MPT_MGMT_STATUS_COMMAND_GOOD; + if (reply != NULL) { + ioc->sas_mgmt.status |= MPT_MGMT_STATUS_RF_VALID; + memcpy(ioc->sas_mgmt.reply, reply, + min(ioc->reply_sz, 4 * reply->u.reply.MsgLength)); + } + + if (ioc->sas_mgmt.status & MPT_MGMT_STATUS_PENDING) { + ioc->sas_mgmt.status &= ~MPT_MGMT_STATUS_PENDING; + complete(&ioc->sas_mgmt.done); + return 1; + } + return 0; +} + +static int mptsas_phy_reset(struct sas_phy *phy, int hard_reset) +{ + MPT_ADAPTER *ioc = phy_to_ioc(phy); + SasIoUnitControlRequest_t *req; + SasIoUnitControlReply_t *reply; + MPT_FRAME_HDR *mf; + MPIHeader_t *hdr; + unsigned long timeleft; + int error = -ERESTARTSYS; + + /* FIXME: fusion doesn't allow non-local phy reset */ + if (!scsi_is_sas_phy_local(phy)) + return -EINVAL; + + /* not implemented for expanders */ + if (phy->identify.target_port_protocols & SAS_PROTOCOL_SMP) + return -ENXIO; + + if (mutex_lock_interruptible(&ioc->sas_mgmt.mutex)) + goto out; + + mf = mpt_get_msg_frame(mptsasMgmtCtx, ioc); + if (!mf) { + error = -ENOMEM; + goto out_unlock; + } + + hdr = (MPIHeader_t *) mf; + req = (SasIoUnitControlRequest_t *)mf; + memset(req, 0, sizeof(SasIoUnitControlRequest_t)); + req->Function = MPI_FUNCTION_SAS_IO_UNIT_CONTROL; + req->MsgContext = hdr->MsgContext; + req->Operation = hard_reset ? + MPI_SAS_OP_PHY_HARD_RESET : MPI_SAS_OP_PHY_LINK_RESET; + req->PhyNum = phy->identify.phy_identifier; + + INITIALIZE_MGMT_STATUS(ioc->sas_mgmt.status) + mpt_put_msg_frame(mptsasMgmtCtx, ioc, mf); + + timeleft = wait_for_completion_timeout(&ioc->sas_mgmt.done, + 10 * HZ); + if (!(ioc->sas_mgmt.status & MPT_MGMT_STATUS_COMMAND_GOOD)) { + error = -ETIME; + mpt_free_msg_frame(ioc, mf); + if (ioc->sas_mgmt.status & MPT_MGMT_STATUS_DID_IOCRESET) + goto out_unlock; + if (!timeleft) + mpt_Soft_Hard_ResetHandler(ioc, CAN_SLEEP); + goto out_unlock; + } + + /* a reply frame is expected */ + if ((ioc->sas_mgmt.status & + MPT_MGMT_STATUS_RF_VALID) == 0) { + error = -ENXIO; + goto out_unlock; + } + + /* process the completed Reply Message Frame */ + reply = (SasIoUnitControlReply_t *)ioc->sas_mgmt.reply; + if (reply->IOCStatus != MPI_IOCSTATUS_SUCCESS) { + printk(MYIOC_s_INFO_FMT "%s: IOCStatus=0x%X IOCLogInfo=0x%X\n", + ioc->name, __func__, reply->IOCStatus, reply->IOCLogInfo); + error = -ENXIO; + goto out_unlock; + } + + error = 0; + + out_unlock: + CLEAR_MGMT_STATUS(ioc->sas_mgmt.status) + mutex_unlock(&ioc->sas_mgmt.mutex); + out: + return error; +} + +static int +mptsas_get_enclosure_identifier(struct sas_rphy *rphy, u64 *identifier) +{ + MPT_ADAPTER *ioc = rphy_to_ioc(rphy); + int i, error; + struct mptsas_portinfo *p; + struct mptsas_enclosure enclosure_info; + u64 enclosure_handle; + + mutex_lock(&ioc->sas_topology_mutex); + list_for_each_entry(p, &ioc->sas_topology, list) { + for (i = 0; i < p->num_phys; i++) { + if (p->phy_info[i].attached.sas_address == + rphy->identify.sas_address) { + enclosure_handle = p->phy_info[i]. + attached.handle_enclosure; + goto found_info; + } + } + } + mutex_unlock(&ioc->sas_topology_mutex); + return -ENXIO; + + found_info: + mutex_unlock(&ioc->sas_topology_mutex); + memset(&enclosure_info, 0, sizeof(struct mptsas_enclosure)); + error = mptsas_sas_enclosure_pg0(ioc, &enclosure_info, + (MPI_SAS_ENCLOS_PGAD_FORM_HANDLE << + MPI_SAS_ENCLOS_PGAD_FORM_SHIFT), enclosure_handle); + if (!error) + *identifier = enclosure_info.enclosure_logical_id; + return error; +} + +static int +mptsas_get_bay_identifier(struct sas_rphy *rphy) +{ + MPT_ADAPTER *ioc = rphy_to_ioc(rphy); + struct mptsas_portinfo *p; + int i, rc; + + mutex_lock(&ioc->sas_topology_mutex); + list_for_each_entry(p, &ioc->sas_topology, list) { + for (i = 0; i < p->num_phys; i++) { + if (p->phy_info[i].attached.sas_address == + rphy->identify.sas_address) { + rc = p->phy_info[i].attached.slot; + goto out; + } + } + } + rc = -ENXIO; + out: + mutex_unlock(&ioc->sas_topology_mutex); + return rc; +} + +static int mptsas_smp_handler(struct Scsi_Host *shost, struct sas_rphy *rphy, + struct request *req) +{ + MPT_ADAPTER *ioc = ((MPT_SCSI_HOST *) shost->hostdata)->ioc; + MPT_FRAME_HDR *mf; + SmpPassthroughRequest_t *smpreq; + struct request *rsp = req->next_rq; + int ret; + int flagsLength; + unsigned long timeleft; + char *psge; + dma_addr_t dma_addr_in = 0; + dma_addr_t dma_addr_out = 0; + u64 sas_address = 0; + + if (!rsp) { + printk(MYIOC_s_ERR_FMT "%s: the smp response space is missing\n", + ioc->name, __func__); + return -EINVAL; + } + + /* do we need to support multiple segments? */ + if (bio_multiple_segments(req->bio) || + bio_multiple_segments(rsp->bio)) { + printk(MYIOC_s_ERR_FMT "%s: multiple segments req %u, rsp %u\n", + ioc->name, __func__, blk_rq_bytes(req), blk_rq_bytes(rsp)); + return -EINVAL; + } + + ret = mutex_lock_interruptible(&ioc->sas_mgmt.mutex); + if (ret) + goto out; + + mf = mpt_get_msg_frame(mptsasMgmtCtx, ioc); + if (!mf) { + ret = -ENOMEM; + goto out_unlock; + } + + smpreq = (SmpPassthroughRequest_t *)mf; + memset(smpreq, 0, sizeof(*smpreq)); + + smpreq->RequestDataLength = cpu_to_le16(blk_rq_bytes(req) - 4); + smpreq->Function = MPI_FUNCTION_SMP_PASSTHROUGH; + + if (rphy) + sas_address = rphy->identify.sas_address; + else { + struct mptsas_portinfo *port_info; + + mutex_lock(&ioc->sas_topology_mutex); + port_info = ioc->hba_port_info; + if (port_info && port_info->phy_info) + sas_address = + port_info->phy_info[0].phy->identify.sas_address; + mutex_unlock(&ioc->sas_topology_mutex); + } + + *((u64 *)&smpreq->SASAddress) = cpu_to_le64(sas_address); + + psge = (char *) + (((int *) mf) + (offsetof(SmpPassthroughRequest_t, SGL) / 4)); + + /* request */ + flagsLength = (MPI_SGE_FLAGS_SIMPLE_ELEMENT | + MPI_SGE_FLAGS_END_OF_BUFFER | + MPI_SGE_FLAGS_DIRECTION) + << MPI_SGE_FLAGS_SHIFT; + flagsLength |= (blk_rq_bytes(req) - 4); + + dma_addr_out = pci_map_single(ioc->pcidev, bio_data(req->bio), + blk_rq_bytes(req), PCI_DMA_BIDIRECTIONAL); + if (!dma_addr_out) + goto put_mf; + ioc->add_sge(psge, flagsLength, dma_addr_out); + psge += ioc->SGE_size; + + /* response */ + flagsLength = MPI_SGE_FLAGS_SIMPLE_ELEMENT | + MPI_SGE_FLAGS_SYSTEM_ADDRESS | + MPI_SGE_FLAGS_IOC_TO_HOST | + MPI_SGE_FLAGS_END_OF_BUFFER; + + flagsLength = flagsLength << MPI_SGE_FLAGS_SHIFT; + flagsLength |= blk_rq_bytes(rsp) + 4; + dma_addr_in = pci_map_single(ioc->pcidev, bio_data(rsp->bio), + blk_rq_bytes(rsp), PCI_DMA_BIDIRECTIONAL); + if (!dma_addr_in) + goto unmap; + ioc->add_sge(psge, flagsLength, dma_addr_in); + + INITIALIZE_MGMT_STATUS(ioc->sas_mgmt.status) + mpt_put_msg_frame(mptsasMgmtCtx, ioc, mf); + + timeleft = wait_for_completion_timeout(&ioc->sas_mgmt.done, 10 * HZ); + if (!(ioc->sas_mgmt.status & MPT_MGMT_STATUS_COMMAND_GOOD)) { + ret = -ETIME; + mpt_free_msg_frame(ioc, mf); + mf = NULL; + if (ioc->sas_mgmt.status & MPT_MGMT_STATUS_DID_IOCRESET) + goto unmap; + if (!timeleft) + mpt_Soft_Hard_ResetHandler(ioc, CAN_SLEEP); + goto unmap; + } + mf = NULL; + + if (ioc->sas_mgmt.status & MPT_MGMT_STATUS_RF_VALID) { + SmpPassthroughReply_t *smprep; + + smprep = (SmpPassthroughReply_t *)ioc->sas_mgmt.reply; + memcpy(req->sense, smprep, sizeof(*smprep)); + req->sense_len = sizeof(*smprep); + req->resid_len = 0; + rsp->resid_len -= smprep->ResponseDataLength; + } else { + printk(MYIOC_s_ERR_FMT + "%s: smp passthru reply failed to be returned\n", + ioc->name, __func__); + ret = -ENXIO; + } +unmap: + if (dma_addr_out) + pci_unmap_single(ioc->pcidev, dma_addr_out, blk_rq_bytes(req), + PCI_DMA_BIDIRECTIONAL); + if (dma_addr_in) + pci_unmap_single(ioc->pcidev, dma_addr_in, blk_rq_bytes(rsp), + PCI_DMA_BIDIRECTIONAL); +put_mf: + if (mf) + mpt_free_msg_frame(ioc, mf); +out_unlock: + CLEAR_MGMT_STATUS(ioc->sas_mgmt.status) + mutex_unlock(&ioc->sas_mgmt.mutex); +out: + return ret; +} + +static struct sas_function_template mptsas_transport_functions = { + .get_linkerrors = mptsas_get_linkerrors, + .get_enclosure_identifier = mptsas_get_enclosure_identifier, + .get_bay_identifier = mptsas_get_bay_identifier, + .phy_reset = mptsas_phy_reset, + .smp_handler = mptsas_smp_handler, +}; + +static struct scsi_transport_template *mptsas_transport_template; + +static int +mptsas_sas_io_unit_pg0(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info) +{ + ConfigExtendedPageHeader_t hdr; + CONFIGPARMS cfg; + SasIOUnitPage0_t *buffer; + dma_addr_t dma_handle; + int error, i; + + hdr.PageVersion = MPI_SASIOUNITPAGE0_PAGEVERSION; + hdr.ExtPageLength = 0; + hdr.PageNumber = 0; + hdr.Reserved1 = 0; + hdr.Reserved2 = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_EXTENDED; + hdr.ExtPageType = MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT; + + cfg.cfghdr.ehdr = &hdr; + cfg.physAddr = -1; + cfg.pageAddr = 0; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; /* read */ + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + error = mpt_config(ioc, &cfg); + if (error) + goto out; + if (!hdr.ExtPageLength) { + error = -ENXIO; + goto out; + } + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + &dma_handle); + if (!buffer) { + error = -ENOMEM; + goto out; + } + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + error = mpt_config(ioc, &cfg); + if (error) + goto out_free_consistent; + + port_info->num_phys = buffer->NumPhys; + port_info->phy_info = kcalloc(port_info->num_phys, + sizeof(struct mptsas_phyinfo), GFP_KERNEL); + if (!port_info->phy_info) { + error = -ENOMEM; + goto out_free_consistent; + } + + ioc->nvdata_version_persistent = + le16_to_cpu(buffer->NvdataVersionPersistent); + ioc->nvdata_version_default = + le16_to_cpu(buffer->NvdataVersionDefault); + + for (i = 0; i < port_info->num_phys; i++) { + mptsas_print_phy_data(ioc, &buffer->PhyData[i]); + port_info->phy_info[i].phy_id = i; + port_info->phy_info[i].port_id = + buffer->PhyData[i].Port; + port_info->phy_info[i].negotiated_link_rate = + buffer->PhyData[i].NegotiatedLinkRate; + port_info->phy_info[i].portinfo = port_info; + port_info->phy_info[i].handle = + le16_to_cpu(buffer->PhyData[i].ControllerDevHandle); + } + + out_free_consistent: + pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + buffer, dma_handle); + out: + return error; +} + +static int +mptsas_sas_io_unit_pg1(MPT_ADAPTER *ioc) +{ + ConfigExtendedPageHeader_t hdr; + CONFIGPARMS cfg; + SasIOUnitPage1_t *buffer; + dma_addr_t dma_handle; + int error; + u8 device_missing_delay; + + memset(&hdr, 0, sizeof(ConfigExtendedPageHeader_t)); + memset(&cfg, 0, sizeof(CONFIGPARMS)); + + cfg.cfghdr.ehdr = &hdr; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + cfg.cfghdr.ehdr->PageType = MPI_CONFIG_PAGETYPE_EXTENDED; + cfg.cfghdr.ehdr->ExtPageType = MPI_CONFIG_EXTPAGETYPE_SAS_IO_UNIT; + cfg.cfghdr.ehdr->PageVersion = MPI_SASIOUNITPAGE1_PAGEVERSION; + cfg.cfghdr.ehdr->PageNumber = 1; + + error = mpt_config(ioc, &cfg); + if (error) + goto out; + if (!hdr.ExtPageLength) { + error = -ENXIO; + goto out; + } + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + &dma_handle); + if (!buffer) { + error = -ENOMEM; + goto out; + } + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + error = mpt_config(ioc, &cfg); + if (error) + goto out_free_consistent; + + ioc->io_missing_delay = + le16_to_cpu(buffer->IODeviceMissingDelay); + device_missing_delay = buffer->ReportDeviceMissingDelay; + ioc->device_missing_delay = (device_missing_delay & MPI_SAS_IOUNIT1_REPORT_MISSING_UNIT_16) ? + (device_missing_delay & MPI_SAS_IOUNIT1_REPORT_MISSING_TIMEOUT_MASK) * 16 : + device_missing_delay & MPI_SAS_IOUNIT1_REPORT_MISSING_TIMEOUT_MASK; + + out_free_consistent: + pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + buffer, dma_handle); + out: + return error; +} + +static int +mptsas_sas_phy_pg0(MPT_ADAPTER *ioc, struct mptsas_phyinfo *phy_info, + u32 form, u32 form_specific) +{ + ConfigExtendedPageHeader_t hdr; + CONFIGPARMS cfg; + SasPhyPage0_t *buffer; + dma_addr_t dma_handle; + int error; + + hdr.PageVersion = MPI_SASPHY0_PAGEVERSION; + hdr.ExtPageLength = 0; + hdr.PageNumber = 0; + hdr.Reserved1 = 0; + hdr.Reserved2 = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_EXTENDED; + hdr.ExtPageType = MPI_CONFIG_EXTPAGETYPE_SAS_PHY; + + cfg.cfghdr.ehdr = &hdr; + cfg.dir = 0; /* read */ + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + /* Get Phy Pg 0 for each Phy. */ + cfg.physAddr = -1; + cfg.pageAddr = form + form_specific; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + + error = mpt_config(ioc, &cfg); + if (error) + goto out; + + if (!hdr.ExtPageLength) { + error = -ENXIO; + goto out; + } + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + &dma_handle); + if (!buffer) { + error = -ENOMEM; + goto out; + } + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + error = mpt_config(ioc, &cfg); + if (error) + goto out_free_consistent; + + mptsas_print_phy_pg0(ioc, buffer); + + phy_info->hw_link_rate = buffer->HwLinkRate; + phy_info->programmed_link_rate = buffer->ProgrammedLinkRate; + phy_info->identify.handle = le16_to_cpu(buffer->OwnerDevHandle); + phy_info->attached.handle = le16_to_cpu(buffer->AttachedDevHandle); + + out_free_consistent: + pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + buffer, dma_handle); + out: + return error; +} + +static int +mptsas_sas_device_pg0(MPT_ADAPTER *ioc, struct mptsas_devinfo *device_info, + u32 form, u32 form_specific) +{ + ConfigExtendedPageHeader_t hdr; + CONFIGPARMS cfg; + SasDevicePage0_t *buffer; + dma_addr_t dma_handle; + __le64 sas_address; + int error=0; + + hdr.PageVersion = MPI_SASDEVICE0_PAGEVERSION; + hdr.ExtPageLength = 0; + hdr.PageNumber = 0; + hdr.Reserved1 = 0; + hdr.Reserved2 = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_EXTENDED; + hdr.ExtPageType = MPI_CONFIG_EXTPAGETYPE_SAS_DEVICE; + + cfg.cfghdr.ehdr = &hdr; + cfg.pageAddr = form + form_specific; + cfg.physAddr = -1; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; /* read */ + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + memset(device_info, 0, sizeof(struct mptsas_devinfo)); + error = mpt_config(ioc, &cfg); + if (error) + goto out; + if (!hdr.ExtPageLength) { + error = -ENXIO; + goto out; + } + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + &dma_handle); + if (!buffer) { + error = -ENOMEM; + goto out; + } + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + error = mpt_config(ioc, &cfg); + + if (error == MPI_IOCSTATUS_CONFIG_INVALID_PAGE) { + error = -ENODEV; + goto out_free_consistent; + } + + if (error) + goto out_free_consistent; + + mptsas_print_device_pg0(ioc, buffer); + + memset(device_info, 0, sizeof(struct mptsas_devinfo)); + device_info->handle = le16_to_cpu(buffer->DevHandle); + device_info->handle_parent = le16_to_cpu(buffer->ParentDevHandle); + device_info->handle_enclosure = + le16_to_cpu(buffer->EnclosureHandle); + device_info->slot = le16_to_cpu(buffer->Slot); + device_info->phy_id = buffer->PhyNum; + device_info->port_id = buffer->PhysicalPort; + device_info->id = buffer->TargetID; + device_info->phys_disk_num = ~0; + device_info->channel = buffer->Bus; + memcpy(&sas_address, &buffer->SASAddress, sizeof(__le64)); + device_info->sas_address = le64_to_cpu(sas_address); + device_info->device_info = + le32_to_cpu(buffer->DeviceInfo); + device_info->flags = le16_to_cpu(buffer->Flags); + + out_free_consistent: + pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + buffer, dma_handle); + out: + return error; +} + +static int +mptsas_sas_expander_pg0(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info, + u32 form, u32 form_specific) +{ + ConfigExtendedPageHeader_t hdr; + CONFIGPARMS cfg; + SasExpanderPage0_t *buffer; + dma_addr_t dma_handle; + int i, error; + __le64 sas_address; + + memset(port_info, 0, sizeof(struct mptsas_portinfo)); + hdr.PageVersion = MPI_SASEXPANDER0_PAGEVERSION; + hdr.ExtPageLength = 0; + hdr.PageNumber = 0; + hdr.Reserved1 = 0; + hdr.Reserved2 = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_EXTENDED; + hdr.ExtPageType = MPI_CONFIG_EXTPAGETYPE_SAS_EXPANDER; + + cfg.cfghdr.ehdr = &hdr; + cfg.physAddr = -1; + cfg.pageAddr = form + form_specific; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; /* read */ + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + memset(port_info, 0, sizeof(struct mptsas_portinfo)); + error = mpt_config(ioc, &cfg); + if (error) + goto out; + + if (!hdr.ExtPageLength) { + error = -ENXIO; + goto out; + } + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + &dma_handle); + if (!buffer) { + error = -ENOMEM; + goto out; + } + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + error = mpt_config(ioc, &cfg); + if (error == MPI_IOCSTATUS_CONFIG_INVALID_PAGE) { + error = -ENODEV; + goto out_free_consistent; + } + + if (error) + goto out_free_consistent; + + /* save config data */ + port_info->num_phys = (buffer->NumPhys) ? buffer->NumPhys : 1; + port_info->phy_info = kcalloc(port_info->num_phys, + sizeof(struct mptsas_phyinfo), GFP_KERNEL); + if (!port_info->phy_info) { + error = -ENOMEM; + goto out_free_consistent; + } + + memcpy(&sas_address, &buffer->SASAddress, sizeof(__le64)); + for (i = 0; i < port_info->num_phys; i++) { + port_info->phy_info[i].portinfo = port_info; + port_info->phy_info[i].handle = + le16_to_cpu(buffer->DevHandle); + port_info->phy_info[i].identify.sas_address = + le64_to_cpu(sas_address); + port_info->phy_info[i].identify.handle_parent = + le16_to_cpu(buffer->ParentDevHandle); + } + + out_free_consistent: + pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + buffer, dma_handle); + out: + return error; +} + +static int +mptsas_sas_expander_pg1(MPT_ADAPTER *ioc, struct mptsas_phyinfo *phy_info, + u32 form, u32 form_specific) +{ + ConfigExtendedPageHeader_t hdr; + CONFIGPARMS cfg; + SasExpanderPage1_t *buffer; + dma_addr_t dma_handle; + int error=0; + + hdr.PageVersion = MPI_SASEXPANDER1_PAGEVERSION; + hdr.ExtPageLength = 0; + hdr.PageNumber = 1; + hdr.Reserved1 = 0; + hdr.Reserved2 = 0; + hdr.PageType = MPI_CONFIG_PAGETYPE_EXTENDED; + hdr.ExtPageType = MPI_CONFIG_EXTPAGETYPE_SAS_EXPANDER; + + cfg.cfghdr.ehdr = &hdr; + cfg.physAddr = -1; + cfg.pageAddr = form + form_specific; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.dir = 0; /* read */ + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + error = mpt_config(ioc, &cfg); + if (error) + goto out; + + if (!hdr.ExtPageLength) { + error = -ENXIO; + goto out; + } + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + &dma_handle); + if (!buffer) { + error = -ENOMEM; + goto out; + } + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + error = mpt_config(ioc, &cfg); + + if (error == MPI_IOCSTATUS_CONFIG_INVALID_PAGE) { + error = -ENODEV; + goto out_free_consistent; + } + + if (error) + goto out_free_consistent; + + + mptsas_print_expander_pg1(ioc, buffer); + + /* save config data */ + phy_info->phy_id = buffer->PhyIdentifier; + phy_info->port_id = buffer->PhysicalPort; + phy_info->negotiated_link_rate = buffer->NegotiatedLinkRate; + phy_info->programmed_link_rate = buffer->ProgrammedLinkRate; + phy_info->hw_link_rate = buffer->HwLinkRate; + phy_info->identify.handle = le16_to_cpu(buffer->OwnerDevHandle); + phy_info->attached.handle = le16_to_cpu(buffer->AttachedDevHandle); + + out_free_consistent: + pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4, + buffer, dma_handle); + out: + return error; +} + +struct rep_manu_request{ + u8 smp_frame_type; + u8 function; + u8 reserved; + u8 request_length; +}; + +struct rep_manu_reply{ + u8 smp_frame_type; /* 0x41 */ + u8 function; /* 0x01 */ + u8 function_result; + u8 response_length; + u16 expander_change_count; + u8 reserved0[2]; + u8 sas_format:1; + u8 reserved1:7; + u8 reserved2[3]; + u8 vendor_id[SAS_EXPANDER_VENDOR_ID_LEN]; + u8 product_id[SAS_EXPANDER_PRODUCT_ID_LEN]; + u8 product_rev[SAS_EXPANDER_PRODUCT_REV_LEN]; + u8 component_vendor_id[SAS_EXPANDER_COMPONENT_VENDOR_ID_LEN]; + u16 component_id; + u8 component_revision_id; + u8 reserved3; + u8 vendor_specific[8]; +}; + +/** + * mptsas_exp_repmanufacture_info - + * @ioc: per adapter object + * @sas_address: expander sas address + * @edev: the sas_expander_device object + * + * Fills in the sas_expander_device object when SMP port is created. + * + * Returns 0 for success, non-zero for failure. + */ +static int +mptsas_exp_repmanufacture_info(MPT_ADAPTER *ioc, + u64 sas_address, struct sas_expander_device *edev) +{ + MPT_FRAME_HDR *mf; + SmpPassthroughRequest_t *smpreq; + SmpPassthroughReply_t *smprep; + struct rep_manu_reply *manufacture_reply; + struct rep_manu_request *manufacture_request; + int ret; + int flagsLength; + unsigned long timeleft; + char *psge; + unsigned long flags; + void *data_out = NULL; + dma_addr_t data_out_dma = 0; + u32 sz; + + spin_lock_irqsave(&ioc->taskmgmt_lock, flags); + if (ioc->ioc_reset_in_progress) { + spin_unlock_irqrestore(&ioc->taskmgmt_lock, flags); + printk(MYIOC_s_INFO_FMT "%s: host reset in progress!\n", + __func__, ioc->name); + return -EFAULT; + } + spin_unlock_irqrestore(&ioc->taskmgmt_lock, flags); + + ret = mutex_lock_interruptible(&ioc->sas_mgmt.mutex); + if (ret) + goto out; + + mf = mpt_get_msg_frame(mptsasMgmtCtx, ioc); + if (!mf) { + ret = -ENOMEM; + goto out_unlock; + } + + smpreq = (SmpPassthroughRequest_t *)mf; + memset(smpreq, 0, sizeof(*smpreq)); + + sz = sizeof(struct rep_manu_request) + sizeof(struct rep_manu_reply); + + data_out = pci_alloc_consistent(ioc->pcidev, sz, &data_out_dma); + if (!data_out) { + printk(KERN_ERR "Memory allocation failure at %s:%d/%s()!\n", + __FILE__, __LINE__, __func__); + ret = -ENOMEM; + goto put_mf; + } + + manufacture_request = data_out; + manufacture_request->smp_frame_type = 0x40; + manufacture_request->function = 1; + manufacture_request->reserved = 0; + manufacture_request->request_length = 0; + + smpreq->Function = MPI_FUNCTION_SMP_PASSTHROUGH; + smpreq->PhysicalPort = 0xFF; + *((u64 *)&smpreq->SASAddress) = cpu_to_le64(sas_address); + smpreq->RequestDataLength = sizeof(struct rep_manu_request); + + psge = (char *) + (((int *) mf) + (offsetof(SmpPassthroughRequest_t, SGL) / 4)); + + flagsLength = MPI_SGE_FLAGS_SIMPLE_ELEMENT | + MPI_SGE_FLAGS_SYSTEM_ADDRESS | + MPI_SGE_FLAGS_HOST_TO_IOC | + MPI_SGE_FLAGS_END_OF_BUFFER; + flagsLength = flagsLength << MPI_SGE_FLAGS_SHIFT; + flagsLength |= sizeof(struct rep_manu_request); + + ioc->add_sge(psge, flagsLength, data_out_dma); + psge += ioc->SGE_size; + + flagsLength = MPI_SGE_FLAGS_SIMPLE_ELEMENT | + MPI_SGE_FLAGS_SYSTEM_ADDRESS | + MPI_SGE_FLAGS_IOC_TO_HOST | + MPI_SGE_FLAGS_END_OF_BUFFER; + flagsLength = flagsLength << MPI_SGE_FLAGS_SHIFT; + flagsLength |= sizeof(struct rep_manu_reply); + ioc->add_sge(psge, flagsLength, data_out_dma + + sizeof(struct rep_manu_request)); + + INITIALIZE_MGMT_STATUS(ioc->sas_mgmt.status) + mpt_put_msg_frame(mptsasMgmtCtx, ioc, mf); + + timeleft = wait_for_completion_timeout(&ioc->sas_mgmt.done, 10 * HZ); + if (!(ioc->sas_mgmt.status & MPT_MGMT_STATUS_COMMAND_GOOD)) { + ret = -ETIME; + mpt_free_msg_frame(ioc, mf); + mf = NULL; + if (ioc->sas_mgmt.status & MPT_MGMT_STATUS_DID_IOCRESET) + goto out_free; + if (!timeleft) + mpt_Soft_Hard_ResetHandler(ioc, CAN_SLEEP); + goto out_free; + } + + mf = NULL; + + if (ioc->sas_mgmt.status & MPT_MGMT_STATUS_RF_VALID) { + u8 *tmp; + + smprep = (SmpPassthroughReply_t *)ioc->sas_mgmt.reply; + if (le16_to_cpu(smprep->ResponseDataLength) != + sizeof(struct rep_manu_reply)) + goto out_free; + + manufacture_reply = data_out + sizeof(struct rep_manu_request); + strncpy(edev->vendor_id, manufacture_reply->vendor_id, + SAS_EXPANDER_VENDOR_ID_LEN); + strncpy(edev->product_id, manufacture_reply->product_id, + SAS_EXPANDER_PRODUCT_ID_LEN); + strncpy(edev->product_rev, manufacture_reply->product_rev, + SAS_EXPANDER_PRODUCT_REV_LEN); + edev->level = manufacture_reply->sas_format; + if (manufacture_reply->sas_format) { + strncpy(edev->component_vendor_id, + manufacture_reply->component_vendor_id, + SAS_EXPANDER_COMPONENT_VENDOR_ID_LEN); + tmp = (u8 *)&manufacture_reply->component_id; + edev->component_id = tmp[0] << 8 | tmp[1]; + edev->component_revision_id = + manufacture_reply->component_revision_id; + } + } else { + printk(MYIOC_s_ERR_FMT + "%s: smp passthru reply failed to be returned\n", + ioc->name, __func__); + ret = -ENXIO; + } +out_free: + if (data_out_dma) + pci_free_consistent(ioc->pcidev, sz, data_out, data_out_dma); +put_mf: + if (mf) + mpt_free_msg_frame(ioc, mf); +out_unlock: + CLEAR_MGMT_STATUS(ioc->sas_mgmt.status) + mutex_unlock(&ioc->sas_mgmt.mutex); +out: + return ret; + } + +static void +mptsas_parse_device_info(struct sas_identify *identify, + struct mptsas_devinfo *device_info) +{ + u16 protocols; + + identify->sas_address = device_info->sas_address; + identify->phy_identifier = device_info->phy_id; + + /* + * Fill in Phy Initiator Port Protocol. + * Bits 6:3, more than one bit can be set, fall through cases. + */ + protocols = device_info->device_info & 0x78; + identify->initiator_port_protocols = 0; + if (protocols & MPI_SAS_DEVICE_INFO_SSP_INITIATOR) + identify->initiator_port_protocols |= SAS_PROTOCOL_SSP; + if (protocols & MPI_SAS_DEVICE_INFO_STP_INITIATOR) + identify->initiator_port_protocols |= SAS_PROTOCOL_STP; + if (protocols & MPI_SAS_DEVICE_INFO_SMP_INITIATOR) + identify->initiator_port_protocols |= SAS_PROTOCOL_SMP; + if (protocols & MPI_SAS_DEVICE_INFO_SATA_HOST) + identify->initiator_port_protocols |= SAS_PROTOCOL_SATA; + + /* + * Fill in Phy Target Port Protocol. + * Bits 10:7, more than one bit can be set, fall through cases. + */ + protocols = device_info->device_info & 0x780; + identify->target_port_protocols = 0; + if (protocols & MPI_SAS_DEVICE_INFO_SSP_TARGET) + identify->target_port_protocols |= SAS_PROTOCOL_SSP; + if (protocols & MPI_SAS_DEVICE_INFO_STP_TARGET) + identify->target_port_protocols |= SAS_PROTOCOL_STP; + if (protocols & MPI_SAS_DEVICE_INFO_SMP_TARGET) + identify->target_port_protocols |= SAS_PROTOCOL_SMP; + if (protocols & MPI_SAS_DEVICE_INFO_SATA_DEVICE) + identify->target_port_protocols |= SAS_PROTOCOL_SATA; + + /* + * Fill in Attached device type. + */ + switch (device_info->device_info & + MPI_SAS_DEVICE_INFO_MASK_DEVICE_TYPE) { + case MPI_SAS_DEVICE_INFO_NO_DEVICE: + identify->device_type = SAS_PHY_UNUSED; + break; + case MPI_SAS_DEVICE_INFO_END_DEVICE: + identify->device_type = SAS_END_DEVICE; + break; + case MPI_SAS_DEVICE_INFO_EDGE_EXPANDER: + identify->device_type = SAS_EDGE_EXPANDER_DEVICE; + break; + case MPI_SAS_DEVICE_INFO_FANOUT_EXPANDER: + identify->device_type = SAS_FANOUT_EXPANDER_DEVICE; + break; + } +} + +static int mptsas_probe_one_phy(struct device *dev, + struct mptsas_phyinfo *phy_info, int index, int local) +{ + MPT_ADAPTER *ioc; + struct sas_phy *phy; + struct sas_port *port; + int error = 0; + VirtTarget *vtarget; + + if (!dev) { + error = -ENODEV; + goto out; + } + + if (!phy_info->phy) { + phy = sas_phy_alloc(dev, index); + if (!phy) { + error = -ENOMEM; + goto out; + } + } else + phy = phy_info->phy; + + mptsas_parse_device_info(&phy->identify, &phy_info->identify); + + /* + * Set Negotiated link rate. + */ + switch (phy_info->negotiated_link_rate) { + case MPI_SAS_IOUNIT0_RATE_PHY_DISABLED: + phy->negotiated_linkrate = SAS_PHY_DISABLED; + break; + case MPI_SAS_IOUNIT0_RATE_FAILED_SPEED_NEGOTIATION: + phy->negotiated_linkrate = SAS_LINK_RATE_FAILED; + break; + case MPI_SAS_IOUNIT0_RATE_1_5: + phy->negotiated_linkrate = SAS_LINK_RATE_1_5_GBPS; + break; + case MPI_SAS_IOUNIT0_RATE_3_0: + phy->negotiated_linkrate = SAS_LINK_RATE_3_0_GBPS; + break; + case MPI_SAS_IOUNIT0_RATE_6_0: + phy->negotiated_linkrate = SAS_LINK_RATE_6_0_GBPS; + break; + case MPI_SAS_IOUNIT0_RATE_SATA_OOB_COMPLETE: + case MPI_SAS_IOUNIT0_RATE_UNKNOWN: + default: + phy->negotiated_linkrate = SAS_LINK_RATE_UNKNOWN; + break; + } + + /* + * Set Max hardware link rate. + */ + switch (phy_info->hw_link_rate & MPI_SAS_PHY0_PRATE_MAX_RATE_MASK) { + case MPI_SAS_PHY0_HWRATE_MAX_RATE_1_5: + phy->maximum_linkrate_hw = SAS_LINK_RATE_1_5_GBPS; + break; + case MPI_SAS_PHY0_PRATE_MAX_RATE_3_0: + phy->maximum_linkrate_hw = SAS_LINK_RATE_3_0_GBPS; + break; + default: + break; + } + + /* + * Set Max programmed link rate. + */ + switch (phy_info->programmed_link_rate & + MPI_SAS_PHY0_PRATE_MAX_RATE_MASK) { + case MPI_SAS_PHY0_PRATE_MAX_RATE_1_5: + phy->maximum_linkrate = SAS_LINK_RATE_1_5_GBPS; + break; + case MPI_SAS_PHY0_PRATE_MAX_RATE_3_0: + phy->maximum_linkrate = SAS_LINK_RATE_3_0_GBPS; + break; + default: + break; + } + + /* + * Set Min hardware link rate. + */ + switch (phy_info->hw_link_rate & MPI_SAS_PHY0_HWRATE_MIN_RATE_MASK) { + case MPI_SAS_PHY0_HWRATE_MIN_RATE_1_5: + phy->minimum_linkrate_hw = SAS_LINK_RATE_1_5_GBPS; + break; + case MPI_SAS_PHY0_PRATE_MIN_RATE_3_0: + phy->minimum_linkrate_hw = SAS_LINK_RATE_3_0_GBPS; + break; + default: + break; + } + + /* + * Set Min programmed link rate. + */ + switch (phy_info->programmed_link_rate & + MPI_SAS_PHY0_PRATE_MIN_RATE_MASK) { + case MPI_SAS_PHY0_PRATE_MIN_RATE_1_5: + phy->minimum_linkrate = SAS_LINK_RATE_1_5_GBPS; + break; + case MPI_SAS_PHY0_PRATE_MIN_RATE_3_0: + phy->minimum_linkrate = SAS_LINK_RATE_3_0_GBPS; + break; + default: + break; + } + + if (!phy_info->phy) { + + error = sas_phy_add(phy); + if (error) { + sas_phy_free(phy); + goto out; + } + phy_info->phy = phy; + } + + if (!phy_info->attached.handle || + !phy_info->port_details) + goto out; + + port = mptsas_get_port(phy_info); + ioc = phy_to_ioc(phy_info->phy); + + if (phy_info->sas_port_add_phy) { + + if (!port) { + port = sas_port_alloc_num(dev); + if (!port) { + error = -ENOMEM; + goto out; + } + error = sas_port_add(port); + if (error) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: exit at line=%d\n", ioc->name, + __func__, __LINE__)); + goto out; + } + mptsas_set_port(ioc, phy_info, port); + devtprintk(ioc, dev_printk(KERN_DEBUG, &port->dev, + MYIOC_s_FMT "add port %d, sas_addr (0x%llx)\n", + ioc->name, port->port_identifier, + (unsigned long long)phy_info-> + attached.sas_address)); + } + dsaswideprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "sas_port_add_phy: phy_id=%d\n", + ioc->name, phy_info->phy_id)); + sas_port_add_phy(port, phy_info->phy); + phy_info->sas_port_add_phy = 0; + devtprintk(ioc, dev_printk(KERN_DEBUG, &phy_info->phy->dev, + MYIOC_s_FMT "add phy %d, phy-obj (0x%p)\n", ioc->name, + phy_info->phy_id, phy_info->phy)); + } + if (!mptsas_get_rphy(phy_info) && port && !port->rphy) { + + struct sas_rphy *rphy; + struct device *parent; + struct sas_identify identify; + + parent = dev->parent->parent; + /* + * Let the hotplug_work thread handle processing + * the adding/removing of devices that occur + * after start of day. + */ + if (mptsas_is_end_device(&phy_info->attached) && + phy_info->attached.handle_parent) { + goto out; + } + + mptsas_parse_device_info(&identify, &phy_info->attached); + if (scsi_is_host_device(parent)) { + struct mptsas_portinfo *port_info; + int i; + + port_info = ioc->hba_port_info; + + for (i = 0; i < port_info->num_phys; i++) + if (port_info->phy_info[i].identify.sas_address == + identify.sas_address) { + sas_port_mark_backlink(port); + goto out; + } + + } else if (scsi_is_sas_rphy(parent)) { + struct sas_rphy *parent_rphy = dev_to_rphy(parent); + if (identify.sas_address == + parent_rphy->identify.sas_address) { + sas_port_mark_backlink(port); + goto out; + } + } + + switch (identify.device_type) { + case SAS_END_DEVICE: + rphy = sas_end_device_alloc(port); + break; + case SAS_EDGE_EXPANDER_DEVICE: + case SAS_FANOUT_EXPANDER_DEVICE: + rphy = sas_expander_alloc(port, identify.device_type); + break; + default: + rphy = NULL; + break; + } + if (!rphy) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: exit at line=%d\n", ioc->name, + __func__, __LINE__)); + goto out; + } + + rphy->identify = identify; + error = sas_rphy_add(rphy); + if (error) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: exit at line=%d\n", ioc->name, + __func__, __LINE__)); + sas_rphy_free(rphy); + goto out; + } + mptsas_set_rphy(ioc, phy_info, rphy); + if (identify.device_type == SAS_EDGE_EXPANDER_DEVICE || + identify.device_type == SAS_FANOUT_EXPANDER_DEVICE) + mptsas_exp_repmanufacture_info(ioc, + identify.sas_address, + rphy_to_expander_device(rphy)); + } + + /* If the device exists,verify it wasn't previously flagged + as a missing device. If so, clear it */ + vtarget = mptsas_find_vtarget(ioc, + phy_info->attached.channel, + phy_info->attached.id); + if (vtarget && vtarget->inDMD) { + printk(KERN_INFO "Device returned, unsetting inDMD\n"); + vtarget->inDMD = 0; + } + + out: + return error; +} + +static int +mptsas_probe_hba_phys(MPT_ADAPTER *ioc) +{ + struct mptsas_portinfo *port_info, *hba; + int error = -ENOMEM, i; + + hba = kzalloc(sizeof(struct mptsas_portinfo), GFP_KERNEL); + if (! hba) + goto out; + + error = mptsas_sas_io_unit_pg0(ioc, hba); + if (error) + goto out_free_port_info; + + mptsas_sas_io_unit_pg1(ioc); + mutex_lock(&ioc->sas_topology_mutex); + port_info = ioc->hba_port_info; + if (!port_info) { + ioc->hba_port_info = port_info = hba; + ioc->hba_port_num_phy = port_info->num_phys; + list_add_tail(&port_info->list, &ioc->sas_topology); + } else { + for (i = 0; i < hba->num_phys; i++) { + port_info->phy_info[i].negotiated_link_rate = + hba->phy_info[i].negotiated_link_rate; + port_info->phy_info[i].handle = + hba->phy_info[i].handle; + port_info->phy_info[i].port_id = + hba->phy_info[i].port_id; + } + kfree(hba->phy_info); + kfree(hba); + hba = NULL; + } + mutex_unlock(&ioc->sas_topology_mutex); +#if defined(CPQ_CIM) + ioc->num_ports = port_info->num_phys; +#endif + for (i = 0; i < port_info->num_phys; i++) { + mptsas_sas_phy_pg0(ioc, &port_info->phy_info[i], + (MPI_SAS_PHY_PGAD_FORM_PHY_NUMBER << + MPI_SAS_PHY_PGAD_FORM_SHIFT), i); + port_info->phy_info[i].identify.handle = + port_info->phy_info[i].handle; + mptsas_sas_device_pg0(ioc, &port_info->phy_info[i].identify, + (MPI_SAS_DEVICE_PGAD_FORM_HANDLE << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + port_info->phy_info[i].identify.handle); + if (!ioc->hba_port_sas_addr) + ioc->hba_port_sas_addr = + port_info->phy_info[i].identify.sas_address; + port_info->phy_info[i].identify.phy_id = + port_info->phy_info[i].phy_id = i; + if (port_info->phy_info[i].attached.handle) + mptsas_sas_device_pg0(ioc, + &port_info->phy_info[i].attached, + (MPI_SAS_DEVICE_PGAD_FORM_HANDLE << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + port_info->phy_info[i].attached.handle); + } + + mptsas_setup_wide_ports(ioc, port_info); + + for (i = 0; i < port_info->num_phys; i++, ioc->sas_index++) + mptsas_probe_one_phy(&ioc->sh->shost_gendev, + &port_info->phy_info[i], ioc->sas_index, 1); + + return 0; + + out_free_port_info: + kfree(hba); + out: + return error; +} + +static void +mptsas_expander_refresh(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info) +{ + struct mptsas_portinfo *parent; + struct device *parent_dev; + struct sas_rphy *rphy; + int i; + u64 sas_address; /* expander sas address */ + u32 handle; + + handle = port_info->phy_info[0].handle; + sas_address = port_info->phy_info[0].identify.sas_address; + for (i = 0; i < port_info->num_phys; i++) { + mptsas_sas_expander_pg1(ioc, &port_info->phy_info[i], + (MPI_SAS_EXPAND_PGAD_FORM_HANDLE_PHY_NUM << + MPI_SAS_EXPAND_PGAD_FORM_SHIFT), (i << 16) + handle); + + mptsas_sas_device_pg0(ioc, + &port_info->phy_info[i].identify, + (MPI_SAS_DEVICE_PGAD_FORM_HANDLE << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + port_info->phy_info[i].identify.handle); + port_info->phy_info[i].identify.phy_id = + port_info->phy_info[i].phy_id; + + if (port_info->phy_info[i].attached.handle) { + mptsas_sas_device_pg0(ioc, + &port_info->phy_info[i].attached, + (MPI_SAS_DEVICE_PGAD_FORM_HANDLE << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + port_info->phy_info[i].attached.handle); + port_info->phy_info[i].attached.phy_id = + port_info->phy_info[i].phy_id; + } + } + + mutex_lock(&ioc->sas_topology_mutex); + parent = mptsas_find_portinfo_by_handle(ioc, + port_info->phy_info[0].identify.handle_parent); + if (!parent) { + mutex_unlock(&ioc->sas_topology_mutex); + return; + } + for (i = 0, parent_dev = NULL; i < parent->num_phys && !parent_dev; + i++) { + if (parent->phy_info[i].attached.sas_address == sas_address) { + rphy = mptsas_get_rphy(&parent->phy_info[i]); + parent_dev = &rphy->dev; + } + } + mutex_unlock(&ioc->sas_topology_mutex); + + mptsas_setup_wide_ports(ioc, port_info); + for (i = 0; i < port_info->num_phys; i++, ioc->sas_index++) + mptsas_probe_one_phy(parent_dev, &port_info->phy_info[i], + ioc->sas_index, 0); +} + +static void +mptsas_expander_event_add(MPT_ADAPTER *ioc, + MpiEventDataSasExpanderStatusChange_t *expander_data) +{ + struct mptsas_portinfo *port_info; + int i; + __le64 sas_address; + + port_info = kzalloc(sizeof(struct mptsas_portinfo), GFP_KERNEL); + if (!port_info) + BUG(); + port_info->num_phys = (expander_data->NumPhys) ? + expander_data->NumPhys : 1; + port_info->phy_info = kcalloc(port_info->num_phys, + sizeof(struct mptsas_phyinfo), GFP_KERNEL); + if (!port_info->phy_info) + BUG(); + memcpy(&sas_address, &expander_data->SASAddress, sizeof(__le64)); + for (i = 0; i < port_info->num_phys; i++) { + port_info->phy_info[i].portinfo = port_info; + port_info->phy_info[i].handle = + le16_to_cpu(expander_data->DevHandle); + port_info->phy_info[i].identify.sas_address = + le64_to_cpu(sas_address); + port_info->phy_info[i].identify.handle_parent = + le16_to_cpu(expander_data->ParentDevHandle); + } + + mutex_lock(&ioc->sas_topology_mutex); + list_add_tail(&port_info->list, &ioc->sas_topology); + mutex_unlock(&ioc->sas_topology_mutex); + + printk(MYIOC_s_INFO_FMT "add expander: num_phys %d, " + "sas_addr (0x%llx)\n", ioc->name, port_info->num_phys, + (unsigned long long)sas_address); + + mptsas_expander_refresh(ioc, port_info); +} + +/** + * mptsas_delete_expander_siblings - remove siblings attached to expander + * @ioc: Pointer to MPT_ADAPTER structure + * @parent: the parent port_info object + * @expander: the expander port_info object + **/ +static void +mptsas_delete_expander_siblings(MPT_ADAPTER *ioc, struct mptsas_portinfo + *parent, struct mptsas_portinfo *expander) +{ + struct mptsas_phyinfo *phy_info; + struct mptsas_portinfo *port_info; + struct sas_rphy *rphy; + int i; + + phy_info = expander->phy_info; + for (i = 0; i < expander->num_phys; i++, phy_info++) { + rphy = mptsas_get_rphy(phy_info); + if (!rphy) + continue; + if (rphy->identify.device_type == SAS_END_DEVICE) + mptsas_del_end_device(ioc, phy_info); + } + + phy_info = expander->phy_info; + for (i = 0; i < expander->num_phys; i++, phy_info++) { + rphy = mptsas_get_rphy(phy_info); + if (!rphy) + continue; + if (rphy->identify.device_type == + MPI_SAS_DEVICE_INFO_EDGE_EXPANDER || + rphy->identify.device_type == + MPI_SAS_DEVICE_INFO_FANOUT_EXPANDER) { + port_info = mptsas_find_portinfo_by_sas_address(ioc, + rphy->identify.sas_address); + if (!port_info) + continue; + if (port_info == parent) /* backlink rphy */ + continue; + /* + Delete this expander even if the expdevpage is exists + because the parent expander is already deleted + */ + mptsas_expander_delete(ioc, port_info, 1); + } + } +} + + +/** + * mptsas_expander_delete - remove this expander + * @ioc: Pointer to MPT_ADAPTER structure + * @port_info: expander port_info struct + * @force: Flag to forcefully delete the expander + * + **/ + +static void mptsas_expander_delete(MPT_ADAPTER *ioc, + struct mptsas_portinfo *port_info, u8 force) +{ + + struct mptsas_portinfo *parent; + int i; + u64 expander_sas_address; + struct mptsas_phyinfo *phy_info; + struct mptsas_portinfo buffer; + struct mptsas_portinfo_details *port_details; + struct sas_port *port; + + if (!port_info) + return; + + /* see if expander is still there before deleting */ + mptsas_sas_expander_pg0(ioc, &buffer, + (MPI_SAS_EXPAND_PGAD_FORM_HANDLE << + MPI_SAS_EXPAND_PGAD_FORM_SHIFT), + port_info->phy_info[0].identify.handle); + + if (buffer.num_phys) { + kfree(buffer.phy_info); + if (!force) + return; + } + + + /* + * Obtain the port_info instance to the parent port + */ + port_details = NULL; + expander_sas_address = + port_info->phy_info[0].identify.sas_address; + parent = mptsas_find_portinfo_by_handle(ioc, + port_info->phy_info[0].identify.handle_parent); + mptsas_delete_expander_siblings(ioc, parent, port_info); + if (!parent) + goto out; + + /* + * Delete rphys in the parent that point + * to this expander. + */ + phy_info = parent->phy_info; + port = NULL; + for (i = 0; i < parent->num_phys; i++, phy_info++) { + if (!phy_info->phy) + continue; + if (phy_info->attached.sas_address != + expander_sas_address) + continue; + if (!port) { + port = mptsas_get_port(phy_info); + port_details = phy_info->port_details; + } + dev_printk(KERN_DEBUG, &phy_info->phy->dev, + MYIOC_s_FMT "delete phy %d, phy-obj (0x%p)\n", ioc->name, + phy_info->phy_id, phy_info->phy); + sas_port_delete_phy(port, phy_info->phy); + } + if (port) { + dev_printk(KERN_DEBUG, &port->dev, + MYIOC_s_FMT "delete port %d, sas_addr (0x%llx)\n", + ioc->name, port->port_identifier, + (unsigned long long)expander_sas_address); + sas_port_delete(port); + mptsas_port_delete(ioc, port_details); + } + out: + + printk(MYIOC_s_INFO_FMT "delete expander: num_phys %d, " + "sas_addr (0x%llx)\n", ioc->name, port_info->num_phys, + (unsigned long long)expander_sas_address); + + /* + * free link + */ + list_del(&port_info->list); + kfree(port_info->phy_info); + kfree(port_info); +} + + +/** + * mptsas_send_expander_event - expanders events + * @ioc: Pointer to MPT_ADAPTER structure + * @expander_data: event data + * + * + * This function handles adding, removing, and refreshing + * device handles within the expander objects. + */ +static void +mptsas_send_expander_event(struct fw_event_work *fw_event) +{ + MPT_ADAPTER *ioc; + MpiEventDataSasExpanderStatusChange_t *expander_data; + struct mptsas_portinfo *port_info; + __le64 sas_address; + int i; + + ioc = fw_event->ioc; + expander_data = (MpiEventDataSasExpanderStatusChange_t *) + fw_event->event_data; + memcpy(&sas_address, &expander_data->SASAddress, sizeof(__le64)); + sas_address = le64_to_cpu(sas_address); + port_info = mptsas_find_portinfo_by_sas_address(ioc, sas_address); + + if (expander_data->ReasonCode == MPI_EVENT_SAS_EXP_RC_ADDED) { + if (port_info) { + for (i = 0; i < port_info->num_phys; i++) { + port_info->phy_info[i].portinfo = port_info; + port_info->phy_info[i].handle = + le16_to_cpu(expander_data->DevHandle); + port_info->phy_info[i].identify.sas_address = + le64_to_cpu(sas_address); + port_info->phy_info[i].identify.handle_parent = + le16_to_cpu(expander_data->ParentDevHandle); + } + mptsas_expander_refresh(ioc, port_info); + } else if (!port_info && expander_data->NumPhys) + mptsas_expander_event_add(ioc, expander_data); + } else if (expander_data->ReasonCode == + MPI_EVENT_SAS_EXP_RC_NOT_RESPONDING) + mptsas_expander_delete(ioc, port_info, 0); + + mptsas_free_fw_event(ioc, fw_event); +} + + +/** + * mptsas_expander_add - + * @ioc: Pointer to MPT_ADAPTER structure + * @handle: + * + */ +static struct mptsas_portinfo * +mptsas_expander_add(MPT_ADAPTER *ioc, u16 handle) +{ + struct mptsas_portinfo buffer, *port_info; + int i; + + if ((mptsas_sas_expander_pg0(ioc, &buffer, + (MPI_SAS_EXPAND_PGAD_FORM_HANDLE << + MPI_SAS_EXPAND_PGAD_FORM_SHIFT), handle))) + return NULL; + + port_info = kzalloc(sizeof(struct mptsas_portinfo), GFP_ATOMIC); + if (!port_info) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: exit at line=%d\n", ioc->name, + __func__, __LINE__)); + return NULL; + } + port_info->num_phys = buffer.num_phys; + port_info->phy_info = buffer.phy_info; + for (i = 0; i < port_info->num_phys; i++) + port_info->phy_info[i].portinfo = port_info; + mutex_lock(&ioc->sas_topology_mutex); + list_add_tail(&port_info->list, &ioc->sas_topology); + mutex_unlock(&ioc->sas_topology_mutex); + printk(MYIOC_s_INFO_FMT "add expander: num_phys %d, " + "sas_addr (0x%llx)\n", ioc->name, port_info->num_phys, + (unsigned long long)buffer.phy_info[0].identify.sas_address); + mptsas_expander_refresh(ioc, port_info); + return port_info; +} + +static void +mptsas_send_link_status_event(struct fw_event_work *fw_event) +{ + MPT_ADAPTER *ioc; + MpiEventDataSasPhyLinkStatus_t *link_data; + struct mptsas_portinfo *port_info; + struct mptsas_phyinfo *phy_info = NULL; + __le64 sas_address; + u8 phy_num; + u8 link_rate; + + ioc = fw_event->ioc; + link_data = (MpiEventDataSasPhyLinkStatus_t *)fw_event->event_data; + + memcpy(&sas_address, &link_data->SASAddress, sizeof(__le64)); + sas_address = le64_to_cpu(sas_address); + link_rate = link_data->LinkRates >> 4; + phy_num = link_data->PhyNum; + + port_info = mptsas_find_portinfo_by_sas_address(ioc, sas_address); + if (port_info) { + phy_info = &port_info->phy_info[phy_num]; + if (phy_info) + phy_info->negotiated_link_rate = link_rate; + } + + if (link_rate == MPI_SAS_IOUNIT0_RATE_1_5 || + link_rate == MPI_SAS_IOUNIT0_RATE_3_0 || + link_rate == MPI_SAS_IOUNIT0_RATE_6_0) { + + if (!port_info) { + if (ioc->old_sas_discovery_protocal) { + port_info = mptsas_expander_add(ioc, + le16_to_cpu(link_data->DevHandle)); + if (port_info) + goto out; + } + goto out; + } + + if (port_info == ioc->hba_port_info) + mptsas_probe_hba_phys(ioc); + else + mptsas_expander_refresh(ioc, port_info); + } else if (phy_info && phy_info->phy) { + if (link_rate == MPI_SAS_IOUNIT0_RATE_PHY_DISABLED) + phy_info->phy->negotiated_linkrate = + SAS_PHY_DISABLED; + else if (link_rate == + MPI_SAS_IOUNIT0_RATE_FAILED_SPEED_NEGOTIATION) + phy_info->phy->negotiated_linkrate = + SAS_LINK_RATE_FAILED; + else { + phy_info->phy->negotiated_linkrate = + SAS_LINK_RATE_UNKNOWN; + if (ioc->device_missing_delay && + mptsas_is_end_device(&phy_info->attached)) { + struct scsi_device *sdev; + VirtDevice *vdevice; + u8 channel, id; + id = phy_info->attached.id; + channel = phy_info->attached.channel; + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Link down for fw_id %d:fw_channel %d\n", + ioc->name, phy_info->attached.id, + phy_info->attached.channel)); + + shost_for_each_device(sdev, ioc->sh) { + vdevice = sdev->hostdata; + if ((vdevice == NULL) || + (vdevice->vtarget == NULL)) + continue; + if ((vdevice->vtarget->tflags & + MPT_TARGET_FLAGS_RAID_COMPONENT || + vdevice->vtarget->raidVolume)) + continue; + if (vdevice->vtarget->id == id && + vdevice->vtarget->channel == + channel) + devtprintk(ioc, + printk(MYIOC_s_DEBUG_FMT + "SDEV OUTSTANDING CMDS" + "%d\n", ioc->name, + atomic_read(&sdev->device_busy))); + } + + } + } + } + out: + mptsas_free_fw_event(ioc, fw_event); +} + +static void +mptsas_not_responding_devices(MPT_ADAPTER *ioc) +{ + struct mptsas_portinfo buffer, *port_info; + struct mptsas_device_info *sas_info; + struct mptsas_devinfo sas_device; + u32 handle; + VirtTarget *vtarget = NULL; + struct mptsas_phyinfo *phy_info; + u8 found_expander; + int retval, retry_count; + unsigned long flags; + + mpt_findImVolumes(ioc); + + spin_lock_irqsave(&ioc->taskmgmt_lock, flags); + if (ioc->ioc_reset_in_progress) { + dfailprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: exiting due to a parallel reset \n", ioc->name, + __func__)); + spin_unlock_irqrestore(&ioc->taskmgmt_lock, flags); + return; + } + spin_unlock_irqrestore(&ioc->taskmgmt_lock, flags); + + /* devices, logical volumes */ + mutex_lock(&ioc->sas_device_info_mutex); + redo_device_scan: + list_for_each_entry(sas_info, &ioc->sas_device_info_list, list) { + if (sas_info->is_cached) + continue; + if (!sas_info->is_logical_volume) { + sas_device.handle = 0; + retry_count = 0; +retry_page: + retval = mptsas_sas_device_pg0(ioc, &sas_device, + (MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID + << MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + (sas_info->fw.channel << 8) + + sas_info->fw.id); + + if (sas_device.handle) + continue; + if (retval == -EBUSY) { + spin_lock_irqsave(&ioc->taskmgmt_lock, flags); + if (ioc->ioc_reset_in_progress) { + dfailprintk(ioc, + printk(MYIOC_s_DEBUG_FMT + "%s: exiting due to reset\n", + ioc->name, __func__)); + spin_unlock_irqrestore + (&ioc->taskmgmt_lock, flags); + mutex_unlock(&ioc-> + sas_device_info_mutex); + return; + } + spin_unlock_irqrestore(&ioc->taskmgmt_lock, + flags); + } + + if (retval && (retval != -ENODEV)) { + if (retry_count < 10) { + retry_count++; + goto retry_page; + } else { + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s: Config page retry exceeded retry " + "count deleting device 0x%llx\n", + ioc->name, __func__, + sas_info->sas_address)); + } + } + + /* delete device */ + vtarget = mptsas_find_vtarget(ioc, + sas_info->fw.channel, sas_info->fw.id); + + if (vtarget) + vtarget->deleted = 1; + + phy_info = mptsas_find_phyinfo_by_sas_address(ioc, + sas_info->sas_address); + + mptsas_del_end_device(ioc, phy_info); + goto redo_device_scan; + } else + mptsas_volume_delete(ioc, sas_info->fw.id); + } + mutex_unlock(&ioc->sas_device_info_mutex); + + /* expanders */ + mutex_lock(&ioc->sas_topology_mutex); + redo_expander_scan: + list_for_each_entry(port_info, &ioc->sas_topology, list) { + + if (!(port_info->phy_info[0].identify.device_info & + MPI_SAS_DEVICE_INFO_SMP_TARGET)) + continue; + found_expander = 0; + handle = 0xFFFF; + while (!mptsas_sas_expander_pg0(ioc, &buffer, + (MPI_SAS_EXPAND_PGAD_FORM_GET_NEXT_HANDLE << + MPI_SAS_EXPAND_PGAD_FORM_SHIFT), handle) && + !found_expander) { + + handle = buffer.phy_info[0].handle; + if (buffer.phy_info[0].identify.sas_address == + port_info->phy_info[0].identify.sas_address) { + found_expander = 1; + } + kfree(buffer.phy_info); + } + + if (!found_expander) { + mptsas_expander_delete(ioc, port_info, 0); + goto redo_expander_scan; + } + } + mutex_unlock(&ioc->sas_topology_mutex); +} + +/** + * mptsas_probe_expanders - adding expanders + * @ioc: Pointer to MPT_ADAPTER structure + * + **/ +static void +mptsas_probe_expanders(MPT_ADAPTER *ioc) +{ + struct mptsas_portinfo buffer, *port_info; + u32 handle; + int i; + + handle = 0xFFFF; + while (!mptsas_sas_expander_pg0(ioc, &buffer, + (MPI_SAS_EXPAND_PGAD_FORM_GET_NEXT_HANDLE << + MPI_SAS_EXPAND_PGAD_FORM_SHIFT), handle)) { + + handle = buffer.phy_info[0].handle; + port_info = mptsas_find_portinfo_by_sas_address(ioc, + buffer.phy_info[0].identify.sas_address); + + if (port_info) { + /* refreshing handles */ + for (i = 0; i < buffer.num_phys; i++) { + port_info->phy_info[i].handle = handle; + port_info->phy_info[i].identify.handle_parent = + buffer.phy_info[0].identify.handle_parent; + } + mptsas_expander_refresh(ioc, port_info); + kfree(buffer.phy_info); + continue; + } + + port_info = kzalloc(sizeof(struct mptsas_portinfo), GFP_KERNEL); + if (!port_info) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: exit at line=%d\n", ioc->name, + __func__, __LINE__)); + return; + } + port_info->num_phys = buffer.num_phys; + port_info->phy_info = buffer.phy_info; + for (i = 0; i < port_info->num_phys; i++) + port_info->phy_info[i].portinfo = port_info; + mutex_lock(&ioc->sas_topology_mutex); + list_add_tail(&port_info->list, &ioc->sas_topology); + mutex_unlock(&ioc->sas_topology_mutex); + printk(MYIOC_s_INFO_FMT "add expander: num_phys %d, " + "sas_addr (0x%llx)\n", ioc->name, port_info->num_phys, + (unsigned long long)buffer.phy_info[0].identify.sas_address); + mptsas_expander_refresh(ioc, port_info); + } +} + +static void +mptsas_probe_devices(MPT_ADAPTER *ioc) +{ + u16 handle; + struct mptsas_devinfo sas_device; + struct mptsas_phyinfo *phy_info; + + handle = 0xFFFF; + while (!(mptsas_sas_device_pg0(ioc, &sas_device, + MPI_SAS_DEVICE_PGAD_FORM_GET_NEXT_HANDLE, handle))) { + + handle = sas_device.handle; + + if ((sas_device.device_info & + (MPI_SAS_DEVICE_INFO_SSP_TARGET | + MPI_SAS_DEVICE_INFO_STP_TARGET | + MPI_SAS_DEVICE_INFO_SATA_DEVICE)) == 0) + continue; + + /* If there is no FW B_T mapping for this device then continue + * */ + if (!(sas_device.flags & MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT) + || !(sas_device.flags & + MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED)) + continue; + + phy_info = mptsas_refreshing_device_handles(ioc, &sas_device); + if (!phy_info) + continue; + + if (mptsas_get_rphy(phy_info)) + continue; + + mptsas_add_end_device(ioc, phy_info); + } +} + +/** + * mptsas_scan_sas_topology - + * @ioc: Pointer to MPT_ADAPTER structure + * @sas_address: + * + **/ +static void +mptsas_scan_sas_topology(MPT_ADAPTER *ioc) +{ + struct scsi_device *sdev; + int i; + + mptsas_probe_hba_phys(ioc); + mptsas_probe_expanders(ioc); + mptsas_probe_devices(ioc); + + /* + Reporting RAID volumes. + */ + if (!ioc->ir_firmware || !ioc->raid_data.pIocPg2 || + !ioc->raid_data.pIocPg2->NumActiveVolumes) + return; + for (i = 0; i < ioc->raid_data.pIocPg2->NumActiveVolumes; i++) { + sdev = scsi_device_lookup(ioc->sh, MPTSAS_RAID_CHANNEL, + ioc->raid_data.pIocPg2->RaidVolume[i].VolumeID, 0); + if (sdev) { + scsi_device_put(sdev); + continue; + } + printk(MYIOC_s_INFO_FMT "attaching raid volume, channel %d, " + "id %d\n", ioc->name, MPTSAS_RAID_CHANNEL, + ioc->raid_data.pIocPg2->RaidVolume[i].VolumeID); + scsi_add_device(ioc->sh, MPTSAS_RAID_CHANNEL, + ioc->raid_data.pIocPg2->RaidVolume[i].VolumeID, 0); + } +} + + +static void +mptsas_handle_queue_full_event(struct fw_event_work *fw_event) +{ + MPT_ADAPTER *ioc; + EventDataQueueFull_t *qfull_data; + struct mptsas_device_info *sas_info; + struct scsi_device *sdev; + int depth; + int id = -1; + int channel = -1; + int fw_id, fw_channel; + u16 current_depth; + + + ioc = fw_event->ioc; + qfull_data = (EventDataQueueFull_t *)fw_event->event_data; + fw_id = qfull_data->TargetID; + fw_channel = qfull_data->Bus; + current_depth = le16_to_cpu(qfull_data->CurrentDepth); + + /* if hidden raid component, look for the volume id */ + mutex_lock(&ioc->sas_device_info_mutex); + if (mptscsih_is_phys_disk(ioc, fw_channel, fw_id)) { + list_for_each_entry(sas_info, &ioc->sas_device_info_list, + list) { + if (sas_info->is_cached || + sas_info->is_logical_volume) + continue; + if (sas_info->is_hidden_raid_component && + (sas_info->fw.channel == fw_channel && + sas_info->fw.id == fw_id)) { + id = sas_info->volume_id; + channel = MPTSAS_RAID_CHANNEL; + goto out; + } + } + } else { + list_for_each_entry(sas_info, &ioc->sas_device_info_list, + list) { + if (sas_info->is_cached || + sas_info->is_hidden_raid_component || + sas_info->is_logical_volume) + continue; + if (sas_info->fw.channel == fw_channel && + sas_info->fw.id == fw_id) { + id = sas_info->os.id; + channel = sas_info->os.channel; + goto out; + } + } + + } + + out: + mutex_unlock(&ioc->sas_device_info_mutex); + + if (id != -1) { + shost_for_each_device(sdev, ioc->sh) { + if (sdev->id == id && sdev->channel == channel) { + if (current_depth > sdev->queue_depth) { + sdev_printk(KERN_INFO, sdev, + "strange observation, the queue " + "depth is (%d) meanwhile fw queue " + "depth (%d)\n", sdev->queue_depth, + current_depth); + continue; + } + depth = scsi_track_queue_full(sdev, + current_depth - 1); + if (depth > 0) + sdev_printk(KERN_INFO, sdev, + "Queue depth reduced to (%d)\n", + depth); + else if (depth < 0) + sdev_printk(KERN_INFO, sdev, + "Tagged Command Queueing is being " + "disabled\n"); + else if (depth == 0) + sdev_printk(KERN_INFO, sdev, + "Queue depth not changed yet\n"); + } + } + } + + mptsas_free_fw_event(ioc, fw_event); +} + + +static struct mptsas_phyinfo * +mptsas_find_phyinfo_by_sas_address(MPT_ADAPTER *ioc, u64 sas_address) +{ + struct mptsas_portinfo *port_info; + struct mptsas_phyinfo *phy_info = NULL; + int i; + + mutex_lock(&ioc->sas_topology_mutex); + list_for_each_entry(port_info, &ioc->sas_topology, list) { + for (i = 0; i < port_info->num_phys; i++) { + if (!mptsas_is_end_device( + &port_info->phy_info[i].attached)) + continue; + if (port_info->phy_info[i].attached.sas_address + != sas_address) + continue; + phy_info = &port_info->phy_info[i]; + break; + } + } + mutex_unlock(&ioc->sas_topology_mutex); + return phy_info; +} + +/** + * mptsas_find_phyinfo_by_phys_disk_num - + * @ioc: Pointer to MPT_ADAPTER structure + * @phys_disk_num: + * @channel: + * @id: + * + **/ +static struct mptsas_phyinfo * +mptsas_find_phyinfo_by_phys_disk_num(MPT_ADAPTER *ioc, u8 phys_disk_num, + u8 channel, u8 id) +{ + struct mptsas_phyinfo *phy_info = NULL; + struct mptsas_portinfo *port_info; + RaidPhysDiskPage1_t *phys_disk = NULL; + int num_paths; + u64 sas_address = 0; + int i; + + phy_info = NULL; + if (!ioc->raid_data.pIocPg3) + return NULL; + /* dual port support */ + num_paths = mpt_raid_phys_disk_get_num_paths(ioc, phys_disk_num); + if (!num_paths) + goto out; + phys_disk = kzalloc(offsetof(RaidPhysDiskPage1_t, Path) + + (num_paths * sizeof(RAID_PHYS_DISK1_PATH)), GFP_KERNEL); + if (!phys_disk) + goto out; + mpt_raid_phys_disk_pg1(ioc, phys_disk_num, phys_disk); + for (i = 0; i < num_paths; i++) { + if ((phys_disk->Path[i].Flags & 1) != 0) + /* entry no longer valid */ + continue; + if ((id == phys_disk->Path[i].PhysDiskID) && + (channel == phys_disk->Path[i].PhysDiskBus)) { + memcpy(&sas_address, &phys_disk->Path[i].WWID, + sizeof(u64)); + phy_info = mptsas_find_phyinfo_by_sas_address(ioc, + sas_address); + goto out; + } + } + + out: + kfree(phys_disk); + if (phy_info) + return phy_info; + + /* + * Extra code to handle RAID0 case, where the sas_address is not updated + * in phys_disk_page_1 when hotswapped + */ + mutex_lock(&ioc->sas_topology_mutex); + list_for_each_entry(port_info, &ioc->sas_topology, list) { + for (i = 0; i < port_info->num_phys && !phy_info; i++) { + if (!mptsas_is_end_device( + &port_info->phy_info[i].attached)) + continue; + if (port_info->phy_info[i].attached.phys_disk_num == ~0) + continue; + if ((port_info->phy_info[i].attached.phys_disk_num == + phys_disk_num) && + (port_info->phy_info[i].attached.id == id) && + (port_info->phy_info[i].attached.channel == + channel)) + phy_info = &port_info->phy_info[i]; + } + } + mutex_unlock(&ioc->sas_topology_mutex); + return phy_info; +} + +static void +mptsas_reprobe_lun(struct scsi_device *sdev, void *data) +{ + int rc; + + sdev->no_uld_attach = data ? 1 : 0; + rc = scsi_device_reprobe(sdev); +} + +static void +mptsas_reprobe_target(struct scsi_target *starget, int uld_attach) +{ + starget_for_each_device(starget, uld_attach ? (void *)1 : NULL, + mptsas_reprobe_lun); +} + +static void +mptsas_adding_inactive_raid_components(MPT_ADAPTER *ioc, u8 channel, u8 id) +{ + CONFIGPARMS cfg; + ConfigPageHeader_t hdr; + dma_addr_t dma_handle; + pRaidVolumePage0_t buffer = NULL; + RaidPhysDiskPage0_t phys_disk; + int i; + struct mptsas_phyinfo *phy_info; + struct mptsas_devinfo sas_device; + + memset(&cfg, 0 , sizeof(CONFIGPARMS)); + memset(&hdr, 0 , sizeof(ConfigPageHeader_t)); + hdr.PageType = MPI_CONFIG_PAGETYPE_RAID_VOLUME; + cfg.pageAddr = (channel << 8) + id; + cfg.cfghdr.hdr = &hdr; + cfg.action = MPI_CONFIG_ACTION_PAGE_HEADER; + cfg.timeout = SAS_CONFIG_PAGE_TIMEOUT; + + if (mpt_config(ioc, &cfg) != 0) + goto out; + + if (!hdr.PageLength) + goto out; + + buffer = pci_alloc_consistent(ioc->pcidev, hdr.PageLength * 4, + &dma_handle); + + if (!buffer) + goto out; + + cfg.physAddr = dma_handle; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + + if (mpt_config(ioc, &cfg) != 0) + goto out; + + if (!(buffer->VolumeStatus.Flags & + MPI_RAIDVOL0_STATUS_FLAG_VOLUME_INACTIVE)) + goto out; + + if (!buffer->NumPhysDisks) + goto out; + + for (i = 0; i < buffer->NumPhysDisks; i++) { + + if (mpt_raid_phys_disk_pg0(ioc, + buffer->PhysDisk[i].PhysDiskNum, &phys_disk) != 0) + continue; + + if (mptsas_sas_device_pg0(ioc, &sas_device, + (MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + (phys_disk.PhysDiskBus << 8) + + phys_disk.PhysDiskID)) + continue; + + /* If there is no FW B_T mapping for this device then continue + * */ + if (!(sas_device.flags & MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT) + || !(sas_device.flags & + MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED)) + continue; + + + phy_info = mptsas_find_phyinfo_by_sas_address(ioc, + sas_device.sas_address); + mptsas_add_end_device(ioc, phy_info); + } + + out: + if (buffer) + pci_free_consistent(ioc->pcidev, hdr.PageLength * 4, buffer, + dma_handle); +} +/* + * Work queue thread to handle SAS hotplug events + */ +static void +mptsas_hotplug_work(MPT_ADAPTER *ioc, struct fw_event_work *fw_event, + struct mptsas_hotplug_event *hot_plug_info) +{ + struct mptsas_phyinfo *phy_info; + struct scsi_target * starget; + struct mptsas_devinfo sas_device; + VirtTarget *vtarget; + int i; + struct mptsas_portinfo *port_info; + + switch (hot_plug_info->event_type) { + + case MPTSAS_ADD_PHYSDISK: + + if (!ioc->raid_data.pIocPg2) + break; + + for (i = 0; i < ioc->raid_data.pIocPg2->NumActiveVolumes; i++) { + if (ioc->raid_data.pIocPg2->RaidVolume[i].VolumeID == + hot_plug_info->id) { + printk(MYIOC_s_WARN_FMT "firmware bug: unable " + "to add hidden disk - target_id matchs " + "volume_id\n", ioc->name); + mptsas_free_fw_event(ioc, fw_event); + return; + } + } + mpt_findImVolumes(ioc); + + case MPTSAS_ADD_DEVICE: + memset(&sas_device, 0, sizeof(struct mptsas_devinfo)); + mptsas_sas_device_pg0(ioc, &sas_device, + (MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + (hot_plug_info->channel << 8) + + hot_plug_info->id); + + /* If there is no FW B_T mapping for this device then break + * */ + if (!(sas_device.flags & MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT) + || !(sas_device.flags & + MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED)) + break; + + if (!sas_device.handle) + return; + + phy_info = mptsas_refreshing_device_handles(ioc, &sas_device); + /* Only For SATA Device ADD */ + if (!phy_info && (sas_device.device_info & + MPI_SAS_DEVICE_INFO_SATA_DEVICE)) { + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s %d SATA HOT PLUG: " + "parent handle of device %x\n", ioc->name, + __func__, __LINE__, sas_device.handle_parent)); + port_info = mptsas_find_portinfo_by_handle(ioc, + sas_device.handle_parent); + + if (port_info == ioc->hba_port_info) + mptsas_probe_hba_phys(ioc); + else if (port_info) + mptsas_expander_refresh(ioc, port_info); + else { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s %d port info is NULL\n", + ioc->name, __func__, __LINE__)); + break; + } + phy_info = mptsas_refreshing_device_handles + (ioc, &sas_device); + } + + if (!phy_info) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s %d phy info is NULL\n", + ioc->name, __func__, __LINE__)); + break; + } + + if (mptsas_get_rphy(phy_info)) + break; + + mptsas_add_end_device(ioc, phy_info); + break; + + case MPTSAS_DEL_DEVICE: + phy_info = mptsas_find_phyinfo_by_sas_address(ioc, + hot_plug_info->sas_address); + mptsas_del_end_device(ioc, phy_info); + break; + + case MPTSAS_DEL_PHYSDISK: + + mpt_findImVolumes(ioc); + + phy_info = mptsas_find_phyinfo_by_phys_disk_num( + ioc, hot_plug_info->phys_disk_num, + hot_plug_info->channel, + hot_plug_info->id); + mptsas_del_end_device(ioc, phy_info); + break; + + case MPTSAS_ADD_PHYSDISK_REPROBE: + + if (mptsas_sas_device_pg0(ioc, &sas_device, + (MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + (hot_plug_info->channel << 8) + hot_plug_info->id)) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, hot_plug_info->id, __LINE__)); + break; + } + + /* If there is no FW B_T mapping for this device then break + * */ + if (!(sas_device.flags & MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT) + || !(sas_device.flags & + MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED)) + break; + + phy_info = mptsas_find_phyinfo_by_sas_address( + ioc, sas_device.sas_address); + + if (!phy_info) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, hot_plug_info->id, __LINE__)); + break; + } + + starget = mptsas_get_starget(phy_info); + if (!starget) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, hot_plug_info->id, __LINE__)); + break; + } + + vtarget = starget->hostdata; + if (!vtarget) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, hot_plug_info->id, __LINE__)); + break; + } + + mpt_findImVolumes(ioc); + + starget_printk(KERN_INFO, starget, MYIOC_s_FMT "RAID Hidding: " + "fw_channel=%d, fw_id=%d, physdsk %d, sas_addr 0x%llx\n", + ioc->name, hot_plug_info->channel, hot_plug_info->id, + hot_plug_info->phys_disk_num, (unsigned long long) + sas_device.sas_address); + + vtarget->id = hot_plug_info->phys_disk_num; + vtarget->tflags |= MPT_TARGET_FLAGS_RAID_COMPONENT; + phy_info->attached.phys_disk_num = hot_plug_info->phys_disk_num; + mptsas_reprobe_target(starget, 1); + break; + + case MPTSAS_DEL_PHYSDISK_REPROBE: + + if (mptsas_sas_device_pg0(ioc, &sas_device, + (MPI_SAS_DEVICE_PGAD_FORM_BUS_TARGET_ID << + MPI_SAS_DEVICE_PGAD_FORM_SHIFT), + (hot_plug_info->channel << 8) + hot_plug_info->id)) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", + ioc->name, __func__, + hot_plug_info->id, __LINE__)); + break; + } + + /* If there is no FW B_T mapping for this device then break + * */ + if (!(sas_device.flags & MPI_SAS_DEVICE0_FLAGS_DEVICE_PRESENT) + || !(sas_device.flags & + MPI_SAS_DEVICE0_FLAGS_DEVICE_MAPPED)) + break; + + phy_info = mptsas_find_phyinfo_by_sas_address(ioc, + sas_device.sas_address); + if (!phy_info) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, hot_plug_info->id, __LINE__)); + break; + } + + starget = mptsas_get_starget(phy_info); + if (!starget) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, hot_plug_info->id, __LINE__)); + break; + } + + vtarget = starget->hostdata; + if (!vtarget) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, hot_plug_info->id, __LINE__)); + break; + } + + if (!(vtarget->tflags & MPT_TARGET_FLAGS_RAID_COMPONENT)) { + dfailprintk(ioc, printk(MYIOC_s_ERR_FMT + "%s: fw_id=%d exit at line=%d\n", ioc->name, + __func__, hot_plug_info->id, __LINE__)); + break; + } + + mpt_findImVolumes(ioc); + + starget_printk(KERN_INFO, starget, MYIOC_s_FMT "RAID Exposing:" + " fw_channel=%d, fw_id=%d, physdsk %d, sas_addr 0x%llx\n", + ioc->name, hot_plug_info->channel, hot_plug_info->id, + hot_plug_info->phys_disk_num, (unsigned long long) + sas_device.sas_address); + + vtarget->tflags &= ~MPT_TARGET_FLAGS_RAID_COMPONENT; + vtarget->id = hot_plug_info->id; + phy_info->attached.phys_disk_num = ~0; + mptsas_reprobe_target(starget, 0); + mptsas_add_device_component_by_fw(ioc, + hot_plug_info->channel, hot_plug_info->id); + break; + + case MPTSAS_ADD_RAID: + + mpt_findImVolumes(ioc); + printk(MYIOC_s_INFO_FMT "attaching raid volume, channel %d, " + "id %d\n", ioc->name, MPTSAS_RAID_CHANNEL, + hot_plug_info->id); + scsi_add_device(ioc->sh, MPTSAS_RAID_CHANNEL, + hot_plug_info->id, 0); + break; + + case MPTSAS_DEL_RAID: + + mpt_findImVolumes(ioc); + printk(MYIOC_s_INFO_FMT "removing raid volume, channel %d, " + "id %d\n", ioc->name, MPTSAS_RAID_CHANNEL, + hot_plug_info->id); + scsi_remove_device(hot_plug_info->sdev); + scsi_device_put(hot_plug_info->sdev); + break; + + case MPTSAS_ADD_INACTIVE_VOLUME: + + mpt_findImVolumes(ioc); + mptsas_adding_inactive_raid_components(ioc, + hot_plug_info->channel, hot_plug_info->id); + break; + + default: + break; + } + + mptsas_free_fw_event(ioc, fw_event); +} + +static void +mptsas_send_sas_event(struct fw_event_work *fw_event) +{ + MPT_ADAPTER *ioc; + struct mptsas_hotplug_event hot_plug_info; + EVENT_DATA_SAS_DEVICE_STATUS_CHANGE *sas_event_data; + u32 device_info; + u64 sas_address; + + ioc = fw_event->ioc; + sas_event_data = (EVENT_DATA_SAS_DEVICE_STATUS_CHANGE *) + fw_event->event_data; + device_info = le32_to_cpu(sas_event_data->DeviceInfo); + + if ((device_info & + (MPI_SAS_DEVICE_INFO_SSP_TARGET | + MPI_SAS_DEVICE_INFO_STP_TARGET | + MPI_SAS_DEVICE_INFO_SATA_DEVICE)) == 0) { + mptsas_free_fw_event(ioc, fw_event); + return; + } + + if (sas_event_data->ReasonCode == + MPI_EVENT_SAS_DEV_STAT_RC_NO_PERSIST_ADDED) { + mptbase_sas_persist_operation(ioc, + MPI_SAS_OP_CLEAR_NOT_PRESENT); + mptsas_free_fw_event(ioc, fw_event); + return; + } + + switch (sas_event_data->ReasonCode) { + case MPI_EVENT_SAS_DEV_STAT_RC_NOT_RESPONDING: + case MPI_EVENT_SAS_DEV_STAT_RC_ADDED: + memset(&hot_plug_info, 0, sizeof(struct mptsas_hotplug_event)); + hot_plug_info.handle = le16_to_cpu(sas_event_data->DevHandle); + hot_plug_info.channel = sas_event_data->Bus; + hot_plug_info.id = sas_event_data->TargetID; + hot_plug_info.phy_id = sas_event_data->PhyNum; + memcpy(&sas_address, &sas_event_data->SASAddress, + sizeof(u64)); + hot_plug_info.sas_address = le64_to_cpu(sas_address); + hot_plug_info.device_info = device_info; + if (sas_event_data->ReasonCode & + MPI_EVENT_SAS_DEV_STAT_RC_ADDED) + hot_plug_info.event_type = MPTSAS_ADD_DEVICE; + else + hot_plug_info.event_type = MPTSAS_DEL_DEVICE; + mptsas_hotplug_work(ioc, fw_event, &hot_plug_info); + break; + + case MPI_EVENT_SAS_DEV_STAT_RC_NO_PERSIST_ADDED: + mptbase_sas_persist_operation(ioc, + MPI_SAS_OP_CLEAR_NOT_PRESENT); + mptsas_free_fw_event(ioc, fw_event); + break; + + case MPI_EVENT_SAS_DEV_STAT_RC_SMART_DATA: + /* TODO */ + case MPI_EVENT_SAS_DEV_STAT_RC_INTERNAL_DEVICE_RESET: + /* TODO */ + default: + mptsas_free_fw_event(ioc, fw_event); + break; + } +} + +static void +mptsas_send_raid_event(struct fw_event_work *fw_event) +{ + MPT_ADAPTER *ioc; + EVENT_DATA_RAID *raid_event_data; + struct mptsas_hotplug_event hot_plug_info; + int status; + int state; + struct scsi_device *sdev = NULL; + VirtDevice *vdevice = NULL; + RaidPhysDiskPage0_t phys_disk; + + ioc = fw_event->ioc; + raid_event_data = (EVENT_DATA_RAID *)fw_event->event_data; + status = le32_to_cpu(raid_event_data->SettingsStatus); + state = (status >> 8) & 0xff; + + memset(&hot_plug_info, 0, sizeof(struct mptsas_hotplug_event)); + hot_plug_info.id = raid_event_data->VolumeID; + hot_plug_info.channel = raid_event_data->VolumeBus; + hot_plug_info.phys_disk_num = raid_event_data->PhysDiskNum; + + if (raid_event_data->ReasonCode == MPI_EVENT_RAID_RC_VOLUME_DELETED || + raid_event_data->ReasonCode == MPI_EVENT_RAID_RC_VOLUME_CREATED || + raid_event_data->ReasonCode == + MPI_EVENT_RAID_RC_VOLUME_STATUS_CHANGED) { + sdev = scsi_device_lookup(ioc->sh, MPTSAS_RAID_CHANNEL, + hot_plug_info.id, 0); + hot_plug_info.sdev = sdev; + if (sdev) + vdevice = sdev->hostdata; + } + + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Entering %s: " + "ReasonCode=%02x\n", ioc->name, __func__, + raid_event_data->ReasonCode)); + + switch (raid_event_data->ReasonCode) { + case MPI_EVENT_RAID_RC_PHYSDISK_DELETED: + hot_plug_info.event_type = MPTSAS_DEL_PHYSDISK_REPROBE; + break; + case MPI_EVENT_RAID_RC_PHYSDISK_CREATED: + hot_plug_info.event_type = MPTSAS_ADD_PHYSDISK_REPROBE; + break; + case MPI_EVENT_RAID_RC_PHYSDISK_STATUS_CHANGED: + switch (state) { + case MPI_PD_STATE_ONLINE: + case MPI_PD_STATE_NOT_COMPATIBLE: + mpt_raid_phys_disk_pg0(ioc, + raid_event_data->PhysDiskNum, &phys_disk); + hot_plug_info.id = phys_disk.PhysDiskID; + hot_plug_info.channel = phys_disk.PhysDiskBus; + hot_plug_info.event_type = MPTSAS_ADD_PHYSDISK; + break; + case MPI_PD_STATE_FAILED: + case MPI_PD_STATE_MISSING: + case MPI_PD_STATE_OFFLINE_AT_HOST_REQUEST: + case MPI_PD_STATE_FAILED_AT_HOST_REQUEST: + case MPI_PD_STATE_OFFLINE_FOR_ANOTHER_REASON: + hot_plug_info.event_type = MPTSAS_DEL_PHYSDISK; + break; + default: + break; + } + break; + case MPI_EVENT_RAID_RC_VOLUME_DELETED: + if (!sdev) + break; + vdevice->vtarget->deleted = 1; /* block IO */ + hot_plug_info.event_type = MPTSAS_DEL_RAID; + break; + case MPI_EVENT_RAID_RC_VOLUME_CREATED: + if (sdev) { + scsi_device_put(sdev); + break; + } + hot_plug_info.event_type = MPTSAS_ADD_RAID; + break; + case MPI_EVENT_RAID_RC_VOLUME_STATUS_CHANGED: + if (!(status & MPI_RAIDVOL0_STATUS_FLAG_ENABLED)) { + if (!sdev) + break; + vdevice->vtarget->deleted = 1; /* block IO */ + hot_plug_info.event_type = MPTSAS_DEL_RAID; + break; + } + switch (state) { + case MPI_RAIDVOL0_STATUS_STATE_FAILED: + case MPI_RAIDVOL0_STATUS_STATE_MISSING: + if (!sdev) + break; + vdevice->vtarget->deleted = 1; /* block IO */ + hot_plug_info.event_type = MPTSAS_DEL_RAID; + break; + case MPI_RAIDVOL0_STATUS_STATE_OPTIMAL: + case MPI_RAIDVOL0_STATUS_STATE_DEGRADED: + if (sdev) { + scsi_device_put(sdev); + break; + } + hot_plug_info.event_type = MPTSAS_ADD_RAID; + break; + default: + break; + } + break; + default: + break; + } + + if (hot_plug_info.event_type != MPTSAS_IGNORE_EVENT) + mptsas_hotplug_work(ioc, fw_event, &hot_plug_info); + else + mptsas_free_fw_event(ioc, fw_event); +} + +/** + * mptsas_issue_tm - send mptsas internal tm request + * @ioc: Pointer to MPT_ADAPTER structure + * @type: Task Management type + * @channel: channel number for task management + * @id: Logical Target ID for reset (if appropriate) + * @lun: Logical unit for reset (if appropriate) + * @task_context: Context for the task to be aborted + * @timeout: timeout for task management control + * + * return 0 on success and -1 on failure: + * + */ +static int +mptsas_issue_tm(MPT_ADAPTER *ioc, u8 type, u8 channel, u8 id, u64 lun, + int task_context, ulong timeout, u8 *issue_reset) +{ + MPT_FRAME_HDR *mf; + SCSITaskMgmt_t *pScsiTm; + int retval; + unsigned long timeleft; + + *issue_reset = 0; + mf = mpt_get_msg_frame(mptsasDeviceResetCtx, ioc); + if (mf == NULL) { + retval = -1; /* return failure */ + dtmprintk(ioc, printk(MYIOC_s_WARN_FMT "TaskMgmt request: no " + "msg frames!!\n", ioc->name)); + goto out; + } + + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT "TaskMgmt request: mr = %p, " + "task_type = 0x%02X,\n\t timeout = %ld, fw_channel = %d, " + "fw_id = %d, lun = %lld,\n\t task_context = 0x%x\n", ioc->name, mf, + type, timeout, channel, id, (unsigned long long)lun, + task_context)); + + pScsiTm = (SCSITaskMgmt_t *) mf; + memset(pScsiTm, 0, sizeof(SCSITaskMgmt_t)); + pScsiTm->Function = MPI_FUNCTION_SCSI_TASK_MGMT; + pScsiTm->TaskType = type; + pScsiTm->MsgFlags = 0; + pScsiTm->TargetID = id; + pScsiTm->Bus = channel; + pScsiTm->ChainOffset = 0; + pScsiTm->Reserved = 0; + pScsiTm->Reserved1 = 0; + pScsiTm->TaskMsgContext = task_context; + int_to_scsilun(lun, (struct scsi_lun *)pScsiTm->LUN); + + INITIALIZE_MGMT_STATUS(ioc->taskmgmt_cmds.status) + CLEAR_MGMT_STATUS(ioc->internal_cmds.status) + retval = 0; + mpt_put_msg_frame_hi_pri(mptsasDeviceResetCtx, ioc, mf); + + /* Now wait for the command to complete */ + timeleft = wait_for_completion_timeout(&ioc->taskmgmt_cmds.done, + timeout*HZ); + if (!(ioc->taskmgmt_cmds.status & MPT_MGMT_STATUS_COMMAND_GOOD)) { + retval = -1; /* return failure */ + dtmprintk(ioc, printk(MYIOC_s_ERR_FMT + "TaskMgmt request: TIMED OUT!(mr=%p)\n", ioc->name, mf)); + mpt_free_msg_frame(ioc, mf); + if (ioc->taskmgmt_cmds.status & MPT_MGMT_STATUS_DID_IOCRESET) + goto out; + *issue_reset = 1; + goto out; + } + + if (!(ioc->taskmgmt_cmds.status & MPT_MGMT_STATUS_RF_VALID)) { + retval = -1; /* return failure */ + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "TaskMgmt request: failed with no reply\n", ioc->name)); + goto out; + } + + out: + CLEAR_MGMT_STATUS(ioc->taskmgmt_cmds.status) + return retval; +} + +/** + * mptsas_broadcast_primative_work - Handle broadcast primitives + * @work: work queue payload containing info describing the event + * + * this will be handled in workqueue context. + */ +static void +mptsas_broadcast_primative_work(struct fw_event_work *fw_event) +{ + MPT_ADAPTER *ioc = fw_event->ioc; + MPT_FRAME_HDR *mf; + VirtDevice *vdevice; + int ii; + struct scsi_cmnd *sc; + SCSITaskMgmtReply_t *pScsiTmReply; + u8 issue_reset; + int task_context; + u8 channel, id; + int lun; + u32 termination_count; + u32 query_count; + + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s - enter\n", ioc->name, __func__)); + + mutex_lock(&ioc->taskmgmt_cmds.mutex); + if (mpt_set_taskmgmt_in_progress_flag(ioc) != 0) { + mutex_unlock(&ioc->taskmgmt_cmds.mutex); + mptsas_requeue_fw_event(ioc, fw_event, 1000); + return; + } + + issue_reset = 0; + termination_count = 0; + query_count = 0; + mpt_findImVolumes(ioc); + pScsiTmReply = (SCSITaskMgmtReply_t *) ioc->taskmgmt_cmds.reply; + + for (ii = 0; ii < ioc->req_depth; ii++) { + if (ioc->fw_events_off) + goto out; + sc = mptscsih_get_scsi_lookup(ioc, ii); + if (!sc) + continue; + mf = MPT_INDEX_2_MFPTR(ioc, ii); + if (!mf) + continue; + task_context = mf->u.frame.hwhdr.msgctxu.MsgContext; + vdevice = sc->device->hostdata; + if (!vdevice || !vdevice->vtarget) + continue; + if (vdevice->vtarget->tflags & MPT_TARGET_FLAGS_RAID_COMPONENT) + continue; /* skip hidden raid components */ + if (vdevice->vtarget->raidVolume) + continue; /* skip hidden raid components */ + channel = vdevice->vtarget->channel; + id = vdevice->vtarget->id; + lun = vdevice->lun; + if (mptsas_issue_tm(ioc, MPI_SCSITASKMGMT_TASKTYPE_QUERY_TASK, + channel, id, (u64)lun, task_context, 30, &issue_reset)) + goto out; + query_count++; + termination_count += + le32_to_cpu(pScsiTmReply->TerminationCount); + if ((pScsiTmReply->IOCStatus == MPI_IOCSTATUS_SUCCESS) && + (pScsiTmReply->ResponseCode == + MPI_SCSITASKMGMT_RSP_TM_SUCCEEDED || + pScsiTmReply->ResponseCode == + MPI_SCSITASKMGMT_RSP_IO_QUEUED_ON_IOC)) + continue; + if (mptsas_issue_tm(ioc, + MPI_SCSITASKMGMT_TASKTYPE_ABRT_TASK_SET, + channel, id, (u64)lun, 0, 30, &issue_reset)) + goto out; + termination_count += + le32_to_cpu(pScsiTmReply->TerminationCount); + } + + out: + dtmprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "%s - exit, query_count = %d termination_count = %d\n", + ioc->name, __func__, query_count, termination_count)); + + ioc->broadcast_aen_busy = 0; + mpt_clear_taskmgmt_in_progress_flag(ioc); + mutex_unlock(&ioc->taskmgmt_cmds.mutex); + + if (issue_reset) { + printk(MYIOC_s_WARN_FMT + "Issuing Reset from %s!! doorbell=0x%08x\n", + ioc->name, __func__, mpt_GetIocState(ioc, 0)); + mpt_Soft_Hard_ResetHandler(ioc, CAN_SLEEP); + } + mptsas_free_fw_event(ioc, fw_event); +} + +/* + * mptsas_send_ir2_event - handle exposing hidden disk when + * an inactive raid volume is added + * + * @ioc: Pointer to MPT_ADAPTER structure + * @ir2_data + * + */ +static void +mptsas_send_ir2_event(struct fw_event_work *fw_event) +{ + MPT_ADAPTER *ioc; + struct mptsas_hotplug_event hot_plug_info; + MPI_EVENT_DATA_IR2 *ir2_data; + u8 reasonCode; + RaidPhysDiskPage0_t phys_disk; + + ioc = fw_event->ioc; + ir2_data = (MPI_EVENT_DATA_IR2 *)fw_event->event_data; + reasonCode = ir2_data->ReasonCode; + + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT "Entering %s: " + "ReasonCode=%02x\n", ioc->name, __func__, reasonCode)); + + memset(&hot_plug_info, 0, sizeof(struct mptsas_hotplug_event)); + hot_plug_info.id = ir2_data->TargetID; + hot_plug_info.channel = ir2_data->Bus; + switch (reasonCode) { + case MPI_EVENT_IR2_RC_FOREIGN_CFG_DETECTED: + hot_plug_info.event_type = MPTSAS_ADD_INACTIVE_VOLUME; + break; + case MPI_EVENT_IR2_RC_DUAL_PORT_REMOVED: + hot_plug_info.phys_disk_num = ir2_data->PhysDiskNum; + hot_plug_info.event_type = MPTSAS_DEL_PHYSDISK; + break; + case MPI_EVENT_IR2_RC_DUAL_PORT_ADDED: + hot_plug_info.phys_disk_num = ir2_data->PhysDiskNum; + mpt_raid_phys_disk_pg0(ioc, + ir2_data->PhysDiskNum, &phys_disk); + hot_plug_info.id = phys_disk.PhysDiskID; + hot_plug_info.event_type = MPTSAS_ADD_PHYSDISK; + break; + default: + mptsas_free_fw_event(ioc, fw_event); + return; + } + mptsas_hotplug_work(ioc, fw_event, &hot_plug_info); +} + +static int +mptsas_event_process(MPT_ADAPTER *ioc, EventNotificationReply_t *reply) +{ + u32 event = le32_to_cpu(reply->Event); + int event_data_sz; + struct fw_event_work *fw_event; + unsigned long delay; + + if (ioc->bus_type != SAS) + return 0; + + /* events turned off due to host reset or driver unloading */ + if (ioc->fw_events_off) + return 0; + + delay = msecs_to_jiffies(1); + switch (event) { + case MPI_EVENT_SAS_BROADCAST_PRIMITIVE: + { + EVENT_DATA_SAS_BROADCAST_PRIMITIVE *broadcast_event_data = + (EVENT_DATA_SAS_BROADCAST_PRIMITIVE *)reply->Data; + if (broadcast_event_data->Primitive != + MPI_EVENT_PRIMITIVE_ASYNCHRONOUS_EVENT) + return 0; + if (ioc->broadcast_aen_busy) + return 0; + ioc->broadcast_aen_busy = 1; + break; + } + case MPI_EVENT_SAS_DEVICE_STATUS_CHANGE: + { + EVENT_DATA_SAS_DEVICE_STATUS_CHANGE *sas_event_data = + (EVENT_DATA_SAS_DEVICE_STATUS_CHANGE *)reply->Data; + u16 ioc_stat; + ioc_stat = le16_to_cpu(reply->IOCStatus); + + if (sas_event_data->ReasonCode == + MPI_EVENT_SAS_DEV_STAT_RC_NOT_RESPONDING) { + mptsas_target_reset_queue(ioc, sas_event_data); + return 0; + } + if (sas_event_data->ReasonCode == + MPI_EVENT_SAS_DEV_STAT_RC_INTERNAL_DEVICE_RESET && + ioc->device_missing_delay && + (ioc_stat & MPI_IOCSTATUS_FLAG_LOG_INFO_AVAILABLE)) { + VirtTarget *vtarget = NULL; + u8 id, channel; + + id = sas_event_data->TargetID; + channel = sas_event_data->Bus; + + vtarget = mptsas_find_vtarget(ioc, channel, id); + if (vtarget) { + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "LogInfo (0x%x) available for " + "INTERNAL_DEVICE_RESET" + "fw_id %d fw_channel %d\n", ioc->name, + le32_to_cpu(reply->IOCLogInfo), + id, channel)); + if (vtarget->raidVolume) { + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Skipping Raid Volume for inDMD\n", + ioc->name)); + } else { + devtprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Setting device flag inDMD\n", + ioc->name)); + vtarget->inDMD = 1; + } + + } + + } + + break; + } + case MPI_EVENT_SAS_EXPANDER_STATUS_CHANGE: + { + MpiEventDataSasExpanderStatusChange_t *expander_data = + (MpiEventDataSasExpanderStatusChange_t *)reply->Data; + + if (ioc->old_sas_discovery_protocal) + return 0; + + if (expander_data->ReasonCode == + MPI_EVENT_SAS_EXP_RC_NOT_RESPONDING && + ioc->device_missing_delay) + delay = HZ * ioc->device_missing_delay; + break; + } + case MPI_EVENT_SAS_DISCOVERY: + { + u32 discovery_status; + EventDataSasDiscovery_t *discovery_data = + (EventDataSasDiscovery_t *)reply->Data; + + discovery_status = le32_to_cpu(discovery_data->DiscoveryStatus); + ioc->sas_discovery_quiesce_io = discovery_status ? 1 : 0; + if (ioc->old_sas_discovery_protocal && !discovery_status) + mptsas_queue_rescan(ioc); + return 0; + } + case MPI_EVENT_INTEGRATED_RAID: + case MPI_EVENT_PERSISTENT_TABLE_FULL: + case MPI_EVENT_IR2: + case MPI_EVENT_SAS_PHY_LINK_STATUS: + case MPI_EVENT_QUEUE_FULL: + break; + default: + return 0; + } + + event_data_sz = ((reply->MsgLength * 4) - + offsetof(EventNotificationReply_t, Data)); + fw_event = kzalloc(sizeof(*fw_event) + event_data_sz, GFP_ATOMIC); + if (!fw_event) { + printk(MYIOC_s_WARN_FMT "%s: failed at (line=%d)\n", ioc->name, + __func__, __LINE__); + return 0; + } + memcpy(fw_event->event_data, reply->Data, event_data_sz); + fw_event->event = event; + fw_event->ioc = ioc; + mptsas_add_fw_event(ioc, fw_event, delay); + return 0; +} + +/* Delete a volume when no longer listed in ioc pg2 + */ +static void mptsas_volume_delete(MPT_ADAPTER *ioc, u8 id) +{ + struct scsi_device *sdev; + int i; + + sdev = scsi_device_lookup(ioc->sh, MPTSAS_RAID_CHANNEL, id, 0); + if (!sdev) + return; + if (!ioc->raid_data.pIocPg2) + goto out; + if (!ioc->raid_data.pIocPg2->NumActiveVolumes) + goto out; + for (i = 0; i < ioc->raid_data.pIocPg2->NumActiveVolumes; i++) + if (ioc->raid_data.pIocPg2->RaidVolume[i].VolumeID == id) + goto release_sdev; + out: + printk(MYIOC_s_INFO_FMT "removing raid volume, channel %d, " + "id %d\n", ioc->name, MPTSAS_RAID_CHANNEL, id); + scsi_remove_device(sdev); + release_sdev: + scsi_device_put(sdev); +} + +static int +mptsas_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct Scsi_Host *sh; + MPT_SCSI_HOST *hd; + MPT_ADAPTER *ioc; + unsigned long flags; + int ii; + int numSGE = 0; + int scale; + int ioc_cap; + int error=0; + int r; + + r = mpt_attach(pdev,id); + if (r) + return r; + + ioc = pci_get_drvdata(pdev); + mptsas_fw_event_off(ioc); + ioc->DoneCtx = mptsasDoneCtx; + ioc->TaskCtx = mptsasTaskCtx; + ioc->InternalCtx = mptsasInternalCtx; + ioc->schedule_target_reset = &mptsas_schedule_target_reset; + ioc->schedule_dead_ioc_flush_running_cmds = + &mptscsih_flush_running_cmds; + /* Added sanity check on readiness of the MPT adapter. + */ + if (ioc->last_state != MPI_IOC_STATE_OPERATIONAL) { + printk(MYIOC_s_WARN_FMT + "Skipping because it's not operational!\n", + ioc->name); + error = -ENODEV; + goto out_mptsas_probe; + } + + if (!ioc->active) { + printk(MYIOC_s_WARN_FMT "Skipping because it's disabled!\n", + ioc->name); + error = -ENODEV; + goto out_mptsas_probe; + } + + /* Sanity check - ensure at least 1 port is INITIATOR capable + */ + ioc_cap = 0; + for (ii = 0; ii < ioc->facts.NumberOfPorts; ii++) { + if (ioc->pfacts[ii].ProtocolFlags & + MPI_PORTFACTS_PROTOCOL_INITIATOR) + ioc_cap++; + } + + if (!ioc_cap) { + printk(MYIOC_s_WARN_FMT + "Skipping ioc=%p because SCSI Initiator mode " + "is NOT enabled!\n", ioc->name, ioc); + return 0; + } + + sh = scsi_host_alloc(&mptsas_driver_template, sizeof(MPT_SCSI_HOST)); + if (!sh) { + printk(MYIOC_s_WARN_FMT + "Unable to register controller with SCSI subsystem\n", + ioc->name); + error = -1; + goto out_mptsas_probe; + } + + spin_lock_irqsave(&ioc->FreeQlock, flags); + + /* Attach the SCSI Host to the IOC structure + */ + ioc->sh = sh; + + sh->io_port = 0; + sh->n_io_port = 0; + sh->irq = 0; + + /* set 16 byte cdb's */ + sh->max_cmd_len = 16; + sh->can_queue = min_t(int, ioc->req_depth - 10, sh->can_queue); + sh->max_id = -1; + sh->max_lun = max_lun; + sh->transportt = mptsas_transport_template; + + /* Required entry. + */ + sh->unique_id = ioc->id; + + INIT_LIST_HEAD(&ioc->sas_topology); + mutex_init(&ioc->sas_topology_mutex); + mutex_init(&ioc->sas_discovery_mutex); + mutex_init(&ioc->sas_mgmt.mutex); + init_completion(&ioc->sas_mgmt.done); + + /* Verify that we won't exceed the maximum + * number of chain buffers + * We can optimize: ZZ = req_sz/sizeof(SGE) + * For 32bit SGE's: + * numSGE = 1 + (ZZ-1)*(maxChain -1) + ZZ + * + (req_sz - 64)/sizeof(SGE) + * A slightly different algorithm is required for + * 64bit SGEs. + */ + scale = ioc->req_sz/ioc->SGE_size; + if (ioc->sg_addr_size == sizeof(u64)) { + numSGE = (scale - 1) * + (ioc->facts.MaxChainDepth-1) + scale + + (ioc->req_sz - 60) / ioc->SGE_size; + } else { + numSGE = 1 + (scale - 1) * + (ioc->facts.MaxChainDepth-1) + scale + + (ioc->req_sz - 64) / ioc->SGE_size; + } + + if (numSGE < sh->sg_tablesize) { + /* Reset this value */ + dprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Resetting sg_tablesize to %d from %d\n", + ioc->name, numSGE, sh->sg_tablesize)); + sh->sg_tablesize = numSGE; + } + + if (mpt_loadtime_max_sectors) { + if (mpt_loadtime_max_sectors < 64 || + mpt_loadtime_max_sectors > 8192) { + printk(MYIOC_s_INFO_FMT "Invalid value passed for" + "mpt_loadtime_max_sectors %d." + "Range from 64 to 8192\n", ioc->name, + mpt_loadtime_max_sectors); + } + mpt_loadtime_max_sectors &= 0xFFFFFFFE; + dprintk(ioc, printk(MYIOC_s_DEBUG_FMT + "Resetting max sector to %d from %d\n", + ioc->name, mpt_loadtime_max_sectors, sh->max_sectors)); + sh->max_sectors = mpt_loadtime_max_sectors; + } + + hd = shost_priv(sh); + hd->ioc = ioc; + + /* SCSI needs scsi_cmnd lookup table! + * (with size equal to req_depth*PtrSz!) + */ + ioc->ScsiLookup = kcalloc(ioc->req_depth, sizeof(void *), GFP_ATOMIC); + if (!ioc->ScsiLookup) { + error = -ENOMEM; + spin_unlock_irqrestore(&ioc->FreeQlock, flags); + goto out_mptsas_probe; + } + spin_lock_init(&ioc->scsi_lookup_lock); + + dprintk(ioc, printk(MYIOC_s_DEBUG_FMT "ScsiLookup @ %p\n", + ioc->name, ioc->ScsiLookup)); + + ioc->sas_data.ptClear = mpt_pt_clear; + + hd->last_queue_full = 0; + INIT_LIST_HEAD(&hd->target_reset_list); + INIT_LIST_HEAD(&ioc->sas_device_info_list); + mutex_init(&ioc->sas_device_info_mutex); + + spin_unlock_irqrestore(&ioc->FreeQlock, flags); + + if (ioc->sas_data.ptClear==1) { + mptbase_sas_persist_operation( + ioc, MPI_SAS_OP_CLEAR_ALL_PERSISTENT); + } + + error = scsi_add_host(sh, &ioc->pcidev->dev); + if (error) { + dprintk(ioc, printk(MYIOC_s_ERR_FMT + "scsi_add_host failed\n", ioc->name)); + goto out_mptsas_probe; + } + + /* older firmware doesn't support expander events */ + if ((ioc->facts.HeaderVersion >> 8) < 0xE) + ioc->old_sas_discovery_protocal = 1; + mptsas_scan_sas_topology(ioc); + mptsas_fw_event_on(ioc); + return 0; + + out_mptsas_probe: + + mptscsih_remove(pdev); + return error; +} + +static void +mptsas_shutdown(struct pci_dev *pdev) +{ + MPT_ADAPTER *ioc = pci_get_drvdata(pdev); + + mptsas_fw_event_off(ioc); + mptsas_cleanup_fw_event_q(ioc); +} + +static void mptsas_remove(struct pci_dev *pdev) +{ + MPT_ADAPTER *ioc = pci_get_drvdata(pdev); + struct mptsas_portinfo *p, *n; + int i; + + if (!ioc->sh) { + printk(MYIOC_s_INFO_FMT "IOC is in Target mode\n", ioc->name); + mpt_detach(pdev); + return; + } + + mptsas_shutdown(pdev); + + mptsas_del_device_components(ioc); + + ioc->sas_discovery_ignore_events = 1; + sas_remove_host(ioc->sh); + + mutex_lock(&ioc->sas_topology_mutex); + list_for_each_entry_safe(p, n, &ioc->sas_topology, list) { + list_del(&p->list); + for (i = 0 ; i < p->num_phys ; i++) + mptsas_port_delete(ioc, p->phy_info[i].port_details); + + kfree(p->phy_info); + kfree(p); + } + mutex_unlock(&ioc->sas_topology_mutex); + ioc->hba_port_info = NULL; + mptscsih_remove(pdev); +} + +static struct pci_device_id mptsas_pci_table[] = { + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVID_SAS1064, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVID_SAS1068, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVID_SAS1064E, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVID_SAS1068E, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVID_SAS1078, + PCI_ANY_ID, PCI_ANY_ID }, + { PCI_VENDOR_ID_LSI_LOGIC, MPI_MANUFACTPAGE_DEVID_SAS1068_820XELP, + PCI_ANY_ID, PCI_ANY_ID }, + {0} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(pci, mptsas_pci_table); + + +static struct pci_driver mptsas_driver = { + .name = "mptsas", + .id_table = mptsas_pci_table, + .probe = mptsas_probe, + .remove = mptsas_remove, + .shutdown = mptsas_shutdown, +#ifdef CONFIG_PM + .suspend = mptscsih_suspend, + .resume = mptscsih_resume, +#endif +}; + +static int __init +mptsas_init(void) +{ + int error; + + show_mptmod_ver(my_NAME, my_VERSION); + + mptsas_transport_template = + sas_attach_transport(&mptsas_transport_functions); + if (!mptsas_transport_template) + return -ENODEV; + mptsas_transport_template->eh_timed_out = mptsas_eh_timed_out; + + mptsasDoneCtx = mpt_register(mptscsih_io_done, MPTSAS_DRIVER, + "mptscsih_io_done"); + mptsasTaskCtx = mpt_register(mptscsih_taskmgmt_complete, MPTSAS_DRIVER, + "mptscsih_taskmgmt_complete"); + mptsasInternalCtx = + mpt_register(mptscsih_scandv_complete, MPTSAS_DRIVER, + "mptscsih_scandv_complete"); + mptsasMgmtCtx = mpt_register(mptsas_mgmt_done, MPTSAS_DRIVER, + "mptsas_mgmt_done"); + mptsasDeviceResetCtx = + mpt_register(mptsas_taskmgmt_complete, MPTSAS_DRIVER, + "mptsas_taskmgmt_complete"); + + mpt_event_register(mptsasDoneCtx, mptsas_event_process); + mpt_reset_register(mptsasDoneCtx, mptsas_ioc_reset); + + error = pci_register_driver(&mptsas_driver); + if (error) + sas_release_transport(mptsas_transport_template); + + return error; +} + +static void __exit +mptsas_exit(void) +{ + pci_unregister_driver(&mptsas_driver); + sas_release_transport(mptsas_transport_template); + + mpt_reset_deregister(mptsasDoneCtx); + mpt_event_deregister(mptsasDoneCtx); + + mpt_deregister(mptsasMgmtCtx); + mpt_deregister(mptsasInternalCtx); + mpt_deregister(mptsasTaskCtx); + mpt_deregister(mptsasDoneCtx); + mpt_deregister(mptsasDeviceResetCtx); +} + +module_init(mptsas_init); +module_exit(mptsas_exit);