@@ -88,6 +88,41 @@ static ssize_t controller_fatal_error_show(struct device *cdev,
}
static DEVICE_ATTR_RO(controller_fatal_error);
+/**
+ * phy_startup_timeout_show - per-phy discovery timeout
+ * @cdev: pointer to embedded class device
+ * @buf: the buffer returned
+ *
+ * A sysfs 'read/write' shost attribute.
+ */
+static ssize_t phy_startup_timeout_show(struct device *cdev,
+ struct device_attribute *attr, char *buf)
+{
+ struct Scsi_Host *shost = class_to_shost(cdev);
+ struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost);
+ struct pm8001_hba_info *pm8001_ha = sha->lldd_ha;
+
+ return snprintf(buf, PAGE_SIZE, "%08xh\n",
+ pm8001_ha->phy_startup_timeout / HZ);
+}
+
+static ssize_t phy_startup_timeout_store(struct device *cdev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct Scsi_Host *shost = class_to_shost(cdev);
+ struct sas_ha_struct *sha = SHOST_TO_SAS_HA(shost);
+ struct pm8001_hba_info *pm8001_ha = sha->lldd_ha;
+ int val = 0;
+
+ if (kstrtoint(buf, 0, &val) < 0)
+ return -EINVAL;
+
+ pm8001_ha->phy_startup_timeout = val * HZ;
+ return strlen(buf);
+}
+
+static DEVICE_ATTR_RW(phy_startup_timeout);
+
/**
* pm8001_ctl_fw_version_show - firmware version
* @cdev: pointer to embedded class device
@@ -867,6 +902,7 @@ static DEVICE_ATTR(update_fw, S_IRUGO|S_IWUSR|S_IWGRP,
struct device_attribute *pm8001_host_attrs[] = {
&dev_attr_interface_rev,
&dev_attr_controller_fatal_error,
+ &dev_attr_phy_startup_timeout,
&dev_attr_fw_version,
&dev_attr_update_fw,
&dev_attr_aap_log,
@@ -141,7 +141,9 @@ enum pm8001_hba_info_flags {
*/
#define PHY_LINK_DISABLE 0x00
#define PHY_LINK_DOWN 0x01
-#define PHY_STATE_LINK_UP_SPCV 0x2
-#define PHY_STATE_LINK_UP_SPC 0x1
+#define PHY_STATE_LINK_UP_SPCV 0x02
+#define PHY_STATE_LINK_UP_SPC 0x01
+#define PHY_STATE_LINK_RESET 0x03
+#define PHY_STATE_HARD_RESET 0x04
#endif
@@ -61,6 +61,26 @@ MODULE_PARM_DESC(staggered_spinup, "enable the staggered spinup feature.\n"
" 0/N: false\n"
" 1/Y: true\n");
+static bool wait_for_phy_startup;
+module_param(wait_for_phy_startup, bool, 0644);
+MODULE_PARM_DESC(wait_for_phy_startup, "wait for phy start.\n"
+ " 0/N: disable\n"
+ " 1/Y: enable\n");
+
+/* if nothing is detected, the PHYs will reset continuously once they
+ * are started. we don't have a good way of differentiating a trained
+ * but waiting-on-signature from one that's never going to train
+ * (nothing attached or dead drive), so we wait an possibly
+ * unreasonable amount of time. this is stuck in start_timeout, and
+ * checked in the host's scan_finished callback for PHYs that haven't
+ * yet come up.
+ *
+ */
+static ulong phy_startup_timeout = 60;
+module_param(phy_startup_timeout, ulong, 0644);
+MODULE_PARM_DESC(phy_startup_timeout_s,
+ " seconds to wait for discovery, per-PHY.");
+
static struct scsi_transport_template *pm8001_stt;
/**
@@ -495,6 +515,8 @@ static struct pm8001_hba_info *pm8001_pci_alloc(struct pci_dev *pdev,
pm8001_ha->id = pm8001_id++;
pm8001_ha->logging_level = logging_level;
pm8001_ha->staggered_spinup = staggered_spinup;
+ pm8001_ha->phy_startup_timeout = phy_startup_timeout * HZ;
+ pm8001_ha->wait_for_phy_startup = wait_for_phy_startup;
pm8001_ha->non_fatal_count = 0;
if (link_rate >= 1 && link_rate <= 15)
pm8001_ha->link_rate = (link_rate << 8);
@@ -39,6 +39,7 @@
*/
#include <linux/slab.h>
+#include "pm80xx_hwi.h"
#include "pm8001_sas.h"
/**
@@ -257,6 +258,59 @@ int pm8001_phy_control(struct asd_sas_phy *sas_phy, enum phy_func func,
return rc;
}
+static int pm8001_update_phy_mask(struct pm8001_hba_info *pm8001_ha)
+{
+ DECLARE_COMPLETION_ONSTACK(comp);
+ unsigned long timeout = msecs_to_jiffies(2000);
+ int ret = 0;
+ int i;
+ struct phy_status *phy_status =
+ kmalloc(sizeof(struct phy_status), GFP_KERNEL);
+ if (phy_status == NULL) {
+ pm8001_printk("Allocation failed\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < pm8001_ha->chip->n_phy; i++) {
+ struct pm8001_phy *phy = pm8001_ha->phy + i;
+
+ if (pm8001_ha->phy_mask & (1 << i) && phy->phy_state) {
+ ret = PM8001_CHIP_DISP->get_phy_profile_req(pm8001_ha,
+ i, SAS_PHY_GENERAL_STATUS_PAGE,
+ &comp, (void *)phy_status);
+ if (ret != 0) {
+ pm8001_printk("get_phy_profile_req:%d:%d\n",
+ i, ret);
+ return ret;
+ }
+ timeout = wait_for_completion_timeout(&comp, timeout);
+ if (timeout == 0)
+ return ret;
+ PM8001_DISC_DBG(pm8001_ha,
+ pm8001_printk("phy_id:%d state:%x nlr:%d plr:%d\n",
+ i,
+ phy_status->phy_state,
+ phy_status->nlr, phy_status->plr));
+ switch (phy_status->phy_state) {
+ case PHY_STATE_LINK_UP_SPCV:
+ case PHY_STATE_LINK_RESET:
+ case PHY_STATE_HARD_RESET:
+ /* these are handled by mpi_hw_event() */
+ break;
+ case PHY_LINK_DOWN:
+ if (time_is_after_jiffies(phy->start_timeout))
+ break;
+ clear_bit(i, &pm8001_ha->phy_mask);
+ break;
+ default:
+ clear_bit(i, &pm8001_ha->phy_mask);
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
/**
* pm8001_scan_start - we should enable all HBA phys by sending the phy_start
* command to HBA.
@@ -274,6 +328,8 @@ void pm8001_scan_start(struct Scsi_Host *shost)
if (pm8001_ha->chip_id == chip_8001)
PM8001_CHIP_DISP->sas_re_init_req(pm8001_ha);
+ pm8001_ha->phy_mask = 0;
+
if (pm8001_ha->pdev->device == 0x8001 ||
pm8001_ha->pdev->device == 0x8081 ||
(pm8001_ha->spinup_interval != 0)) {
@@ -303,11 +359,16 @@ void pm8001_scan_start(struct Scsi_Host *shost)
int pm8001_scan_finished(struct Scsi_Host *shost, unsigned long time)
{
struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost);
+ struct pm8001_hba_info *pm8001_ha = ha->lldd_ha;
- /* give the phy enabling interrupt event time to come in (1s
- * is empirically about all it takes) */
- if (time < HZ)
+ /* Wait for PHY startup to finish */
+ PM8001_DISC_DBG(pm8001_ha,
+ pm8001_printk("mask:%lx\n", pm8001_ha->phy_mask));
+ if (pm8001_ha->phy_mask && pm8001_ha->wait_for_phy_startup) {
+ pm8001_update_phy_mask(pm8001_ha);
return 0;
+ }
+
/* Wait for discovery to finish */
sas_drain_work(ha);
return 1;
@@ -283,6 +283,7 @@ struct pm8001_phy {
struct completion *reset_completion;
bool port_reset_status;
bool reset_success;
+ unsigned long start_timeout;
};
/* port reset status */
@@ -578,6 +579,9 @@ struct pm8001_hba_info {
int phy_head;
int phy_tail;
spinlock_t phy_q_lock;
+ unsigned long phy_mask;
+ u32 phy_startup_timeout;
+ bool wait_for_phy_startup;
};
struct pm8001_work {
@@ -80,18 +80,26 @@ void pm8001_spinup_timedout(struct timer_list *t)
if (i++ >= pm8001_ha->spinup_group && pm8001_ha->spinup_group)
break;
- if (pm8001_ha->phy_head == -1 || pm8001_ha->reset_in_progress)
- break; /* No phys to spinup */
-
- phy_id = pm8001_ha->phy_up[pm8001_ha->phy_head];
- /* Processed phy id, make it invalid 0xff for
- * checking repeated phy ups
- */
- pm8001_ha->phy_up[pm8001_ha->phy_head] = 0xff;
- if (pm8001_ha->phy_head == pm8001_ha->phy_tail) {
- pm8001_ha->phy_head = pm8001_ha->phy_tail = -1;
+ if (pm8001_ha->phy_head == -1 ||
+ pm8001_ha->reset_in_progress) {
+ /* No phys to spinup or timeout */
+ if (pm8001_ha->phy_mask) {
+ pm8001_printk("spinup timeout phy_mask:%lx\n",
+ pm8001_ha->phy_mask);
+ pm8001_ha->phy_mask = 0;
+ }
+ break;
} else {
- pm8001_ha->phy_head =
+ phy_id = pm8001_ha->phy_up[pm8001_ha->phy_head];
+ /* Processed phy id, make it invalid 0xff for
+ * checking repeated phy ups
+ */
+ pm8001_ha->phy_up[pm8001_ha->phy_head] = 0xff;
+ if (pm8001_ha->phy_head == pm8001_ha->phy_tail)
+ pm8001_ha->phy_head =
+ pm8001_ha->phy_tail = -1;
+ else
+ pm8001_ha->phy_head =
(pm8001_ha->phy_head+1) % PM8001_MAX_PHYS;
}
@@ -3584,8 +3592,10 @@ static int mpi_phy_start_resp(struct pm8001_hba_info *pm8001_ha, void *piomb)
phy->enable_completion = NULL;
}
- return 0;
+ if (!phy->phy_state)
+ clear_bit(phy_id, &pm8001_ha->phy_mask);
+ return 0;
}
/**
@@ -3646,6 +3656,36 @@ static int mpi_hw_event(struct pm8001_hba_info *pm8001_ha, void *piomb)
pm8001_printk("portid:%d phyid:%d event:0x%x status:0x%x\n",
port_id, phy_id, eventType, status));
+ switch (eventType) {
+ /* the PHY has completed startup and possibly queued
+ * stuff to the libsas side, so we no longer have to
+ * track that it is in the middle of doing something
+ * that libsas doesn't know about.
+ */
+ case HW_EVENT_SAS_PHY_UP:
+ case HW_EVENT_SATA_PHY_UP:
+ case HW_EVENT_PHY_DOWN:
+ case HW_EVENT_PORT_INVALID:
+ case HW_EVENT_BROADCAST_CHANGE:
+ case HW_EVENT_PHY_ERROR:
+ case HW_EVENT_HARD_RESET_RECEIVED:
+ case HW_EVENT_ID_FRAME_TIMEOUT:
+ case HW_EVENT_LINK_ERR_PHY_RESET_FAILED:
+ case HW_EVENT_PORT_RESET_TIMER_TMO:
+ case HW_EVENT_PORT_RECOVERY_TIMER_TMO:
+ case HW_EVENT_PORT_RECOVER:
+ clear_bit(phy_id, &pm8001_ha->phy_mask);
+ break;
+ /* we may have already gotten the response from the
+ * PHY, but if the PHY was powered off then this event
+ * signals that there is more in-flight and we have
+ * restarted the the spinup timer.
+ */
+ case HW_EVENT_SATA_SPINUP_HOLD:
+ set_bit(phy_id, &pm8001_ha->phy_mask);
+ break;
+ }
+
switch (eventType) {
case HW_EVENT_SAS_PHY_UP:
@@ -4975,6 +5015,9 @@ pm80xx_chip_phy_start_req(struct pm8001_hba_info *pm8001_ha, u8 phy_id)
PM8001_INIT_DBG(pm8001_ha,
pm8001_printk("PHY START REQ for phy_id %d\n", phy_id));
+ set_bit(phy_id, &pm8001_ha->phy_mask);
+ pm8001_ha->phy[phy_id].start_timeout =
+ jiffies + pm8001_ha->phy_startup_timeout;
payload.ase_sh_lm_slr_phyid = cpu_to_le32(SPINHOLD_ENABLE |
LINKMODE_AUTO | pm8001_ha->link_rate | phy_id);