diff options
Diffstat (limited to 'drivers/scsi')
38 files changed, 1483 insertions, 383 deletions
diff --git a/drivers/scsi/aacraid/linit.c b/drivers/scsi/aacraid/linit.c index 21a67ed047e8..ff6caab8cc8b 100644 --- a/drivers/scsi/aacraid/linit.c +++ b/drivers/scsi/aacraid/linit.c @@ -452,10 +452,11 @@ static int aac_slave_configure(struct scsi_device *sdev) else if (depth < 2) depth = 2; scsi_change_queue_depth(sdev, depth); - } else + } else { scsi_change_queue_depth(sdev, 1); sdev->tagged_supported = 1; + } return 0; } diff --git a/drivers/scsi/cxgbi/cxgb4i/cxgb4i.c b/drivers/scsi/cxgbi/cxgb4i/cxgb4i.c index 804806e1cbb4..339f6b7f4803 100644 --- a/drivers/scsi/cxgbi/cxgb4i/cxgb4i.c +++ b/drivers/scsi/cxgbi/cxgb4i/cxgb4i.c @@ -13,6 +13,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ +#include <linux/kernel.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <scsi/scsi_host.h> @@ -158,7 +159,6 @@ static struct scsi_transport_template *cxgb4i_stt; * open/close/abort and data send/receive. */ -#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) #define RCV_BUFSIZ_MASK 0x3FFU #define MAX_IMM_TX_PKT_LEN 256 diff --git a/drivers/scsi/cxgbi/cxgb4i/cxgb4i.h b/drivers/scsi/cxgbi/cxgb4i/cxgb4i.h index 22dd8d670e4a..2fd9c76fc21c 100644 --- a/drivers/scsi/cxgbi/cxgb4i/cxgb4i.h +++ b/drivers/scsi/cxgbi/cxgb4i/cxgb4i.h @@ -25,21 +25,4 @@ #define T5_ISS_VALID (1 << 18) -struct ulptx_idata { - __be32 cmd_more; - __be32 len; -}; - -struct cpl_rx_data_ddp { - union opcode_tid ot; - __be16 urg; - __be16 len; - __be32 seq; - union { - __be32 nxt_seq; - __be32 ddp_report; - }; - __be32 ulp_crc; - __be32 ddpvld; -}; #endif /* __CXGB4I_H__ */ diff --git a/drivers/scsi/cxlflash/common.h b/drivers/scsi/cxlflash/common.h index a8ac4c0a1493..6e6815545a71 100644 --- a/drivers/scsi/cxlflash/common.h +++ b/drivers/scsi/cxlflash/common.h @@ -108,7 +108,6 @@ struct cxlflash_cfg { atomic_t scan_host_needed; struct cxl_afu *cxl_afu; - struct pci_dev *parent_dev; atomic_t recovery_threads; struct mutex ctx_recovery_mutex; diff --git a/drivers/scsi/cxlflash/main.c b/drivers/scsi/cxlflash/main.c index 3879b46d79e1..8fb9643fe6e3 100644 --- a/drivers/scsi/cxlflash/main.c +++ b/drivers/scsi/cxlflash/main.c @@ -289,7 +289,7 @@ static void context_reset(struct afu_cmd *cmd) atomic64_set(&afu->room, room); if (room) goto write_rrin; - udelay(nretry); + udelay(1 << nretry); } while (nretry++ < MC_ROOM_RETRY_CNT); pr_err("%s: no cmd_room to send reset\n", __func__); @@ -303,7 +303,7 @@ write_rrin: if (rrin != 0x1) break; /* Double delay each time */ - udelay(2 << nretry); + udelay(1 << nretry); } while (nretry++ < MC_ROOM_RETRY_CNT); } @@ -338,7 +338,7 @@ retry: atomic64_set(&afu->room, room); if (room) goto write_ioarrin; - udelay(nretry); + udelay(1 << nretry); } while (nretry++ < MC_ROOM_RETRY_CNT); dev_err(dev, "%s: no cmd_room to send 0x%X\n", @@ -352,7 +352,7 @@ retry: * afu->room. */ if (nretry++ < MC_ROOM_RETRY_CNT) { - udelay(nretry); + udelay(1 << nretry); goto retry; } @@ -683,28 +683,23 @@ static void stop_afu(struct cxlflash_cfg *cfg) } /** - * term_mc() - terminates the master context + * term_intr() - disables all AFU interrupts * @cfg: Internal structure associated with the host. * @level: Depth of allocation, where to begin waterfall tear down. * * Safe to call with AFU/MC in partially allocated/initialized state. */ -static void term_mc(struct cxlflash_cfg *cfg, enum undo_level level) +static void term_intr(struct cxlflash_cfg *cfg, enum undo_level level) { - int rc = 0; struct afu *afu = cfg->afu; struct device *dev = &cfg->dev->dev; if (!afu || !cfg->mcctx) { - dev_err(dev, "%s: returning from term_mc with NULL afu or MC\n", - __func__); + dev_err(dev, "%s: returning with NULL afu or MC\n", __func__); return; } switch (level) { - case UNDO_START: - rc = cxl_stop_context(cfg->mcctx); - BUG_ON(rc); case UNMAP_THREE: cxl_unmap_afu_irq(cfg->mcctx, 3, afu); case UNMAP_TWO: @@ -713,9 +708,34 @@ static void term_mc(struct cxlflash_cfg *cfg, enum undo_level level) cxl_unmap_afu_irq(cfg->mcctx, 1, afu); case FREE_IRQ: cxl_free_afu_irqs(cfg->mcctx); - case RELEASE_CONTEXT: - cfg->mcctx = NULL; + /* fall through */ + case UNDO_NOOP: + /* No action required */ + break; + } +} + +/** + * term_mc() - terminates the master context + * @cfg: Internal structure associated with the host. + * @level: Depth of allocation, where to begin waterfall tear down. + * + * Safe to call with AFU/MC in partially allocated/initialized state. + */ +static void term_mc(struct cxlflash_cfg *cfg) +{ + int rc = 0; + struct afu *afu = cfg->afu; + struct device *dev = &cfg->dev->dev; + + if (!afu || !cfg->mcctx) { + dev_err(dev, "%s: returning with NULL afu or MC\n", __func__); + return; } + + rc = cxl_stop_context(cfg->mcctx); + WARN_ON(rc); + cfg->mcctx = NULL; } /** @@ -726,10 +746,20 @@ static void term_mc(struct cxlflash_cfg *cfg, enum undo_level level) */ static void term_afu(struct cxlflash_cfg *cfg) { + /* + * Tear down is carefully orchestrated to ensure + * no interrupts can come in when the problem state + * area is unmapped. + * + * 1) Disable all AFU interrupts + * 2) Unmap the problem state area + * 3) Stop the master context + */ + term_intr(cfg, UNMAP_THREE); if (cfg->afu) stop_afu(cfg); - term_mc(cfg, UNDO_START); + term_mc(cfg); pr_debug("%s: returning\n", __func__); } @@ -1355,7 +1385,7 @@ static int start_context(struct cxlflash_cfg *cfg) */ static int read_vpd(struct cxlflash_cfg *cfg, u64 wwpn[]) { - struct pci_dev *dev = cfg->parent_dev; + struct pci_dev *dev = cfg->dev; int rc = 0; int ro_start, ro_size, i, j, k; ssize_t vpd_size; @@ -1364,7 +1394,7 @@ static int read_vpd(struct cxlflash_cfg *cfg, u64 wwpn[]) char *wwpn_vpd_tags[NUM_FC_PORTS] = { "V5", "V6" }; /* Get the VPD data from the device */ - vpd_size = pci_read_vpd(dev, 0, sizeof(vpd_data), vpd_data); + vpd_size = cxl_read_adapter_vpd(dev, vpd_data, sizeof(vpd_data)); if (unlikely(vpd_size <= 0)) { dev_err(&dev->dev, "%s: Unable to read VPD (size = %ld)\n", __func__, vpd_size); @@ -1597,41 +1627,24 @@ static int start_afu(struct cxlflash_cfg *cfg) } /** - * init_mc() - create and register as the master context + * init_intr() - setup interrupt handlers for the master context * @cfg: Internal structure associated with the host. * * Return: 0 on success, -errno on failure */ -static int init_mc(struct cxlflash_cfg *cfg) +static enum undo_level init_intr(struct cxlflash_cfg *cfg, + struct cxl_context *ctx) { - struct cxl_context *ctx; - struct device *dev = &cfg->dev->dev; struct afu *afu = cfg->afu; + struct device *dev = &cfg->dev->dev; int rc = 0; - enum undo_level level; - - ctx = cxl_get_context(cfg->dev); - if (unlikely(!ctx)) - return -ENOMEM; - cfg->mcctx = ctx; - - /* Set it up as a master with the CXL */ - cxl_set_master(ctx); - - /* During initialization reset the AFU to start from a clean slate */ - rc = cxl_afu_reset(cfg->mcctx); - if (unlikely(rc)) { - dev_err(dev, "%s: initial AFU reset failed rc=%d\n", - __func__, rc); - level = RELEASE_CONTEXT; - goto out; - } + enum undo_level level = UNDO_NOOP; rc = cxl_allocate_afu_irqs(ctx, 3); if (unlikely(rc)) { dev_err(dev, "%s: call to allocate_afu_irqs failed rc=%d!\n", __func__, rc); - level = RELEASE_CONTEXT; + level = UNDO_NOOP; goto out; } @@ -1661,8 +1674,47 @@ static int init_mc(struct cxlflash_cfg *cfg) level = UNMAP_TWO; goto out; } +out: + return level; +} - rc = 0; +/** + * init_mc() - create and register as the master context + * @cfg: Internal structure associated with the host. + * + * Return: 0 on success, -errno on failure + */ +static int init_mc(struct cxlflash_cfg *cfg) +{ + struct cxl_context *ctx; + struct device *dev = &cfg->dev->dev; + int rc = 0; + enum undo_level level; + + ctx = cxl_get_context(cfg->dev); + if (unlikely(!ctx)) { + rc = -ENOMEM; + goto ret; + } + cfg->mcctx = ctx; + + /* Set it up as a master with the CXL */ + cxl_set_master(ctx); + + /* During initialization reset the AFU to start from a clean slate */ + rc = cxl_afu_reset(cfg->mcctx); + if (unlikely(rc)) { + dev_err(dev, "%s: initial AFU reset failed rc=%d\n", + __func__, rc); + goto ret; + } + + level = init_intr(cfg, ctx); + if (unlikely(level)) { + dev_err(dev, "%s: setting up interrupts failed rc=%d\n", + __func__, rc); + goto out; + } /* This performs the equivalent of the CXL_IOCTL_START_WORK. * The CXL_IOCTL_GET_PROCESS_ELEMENT is implicit in the process @@ -1678,7 +1730,7 @@ ret: pr_debug("%s: returning rc=%d\n", __func__, rc); return rc; out: - term_mc(cfg, level); + term_intr(cfg, level); goto ret; } @@ -1751,7 +1803,8 @@ out: err2: kref_put(&afu->mapcount, afu_unmap); err1: - term_mc(cfg, UNDO_START); + term_intr(cfg, UNMAP_THREE); + term_mc(cfg); goto out; } @@ -2350,7 +2403,6 @@ static int cxlflash_probe(struct pci_dev *pdev, { struct Scsi_Host *host; struct cxlflash_cfg *cfg = NULL; - struct device *phys_dev; struct dev_dependent_vals *ddv; int rc = 0; @@ -2416,19 +2468,6 @@ static int cxlflash_probe(struct pci_dev *pdev, pci_set_drvdata(pdev, cfg); - /* - * Use the special service provided to look up the physical - * PCI device, since we are called on the probe of the virtual - * PCI host bus (vphb) - */ - phys_dev = cxl_get_phys_dev(pdev); - if (!dev_is_pci(phys_dev)) { - dev_err(&pdev->dev, "%s: not a pci dev\n", __func__); - rc = -ENODEV; - goto out_remove; - } - cfg->parent_dev = to_pci_dev(phys_dev); - cfg->cxl_afu = cxl_pci_to_afu(pdev); rc = init_pci(cfg); @@ -2502,8 +2541,7 @@ static pci_ers_result_t cxlflash_pci_error_detected(struct pci_dev *pdev, if (unlikely(rc)) dev_err(dev, "%s: Failed to mark user contexts!(%d)\n", __func__, rc); - stop_afu(cfg); - term_mc(cfg, UNDO_START); + term_afu(cfg); return PCI_ERS_RESULT_NEED_RESET; case pci_channel_io_perm_failure: cfg->state = STATE_FAILTERM; diff --git a/drivers/scsi/cxlflash/main.h b/drivers/scsi/cxlflash/main.h index 0faed422c7f4..eb9d8f730b38 100644 --- a/drivers/scsi/cxlflash/main.h +++ b/drivers/scsi/cxlflash/main.h @@ -79,12 +79,11 @@ #define WWPN_BUF_LEN (WWPN_LEN + 1) enum undo_level { - RELEASE_CONTEXT = 0, + UNDO_NOOP = 0, FREE_IRQ, UNMAP_ONE, UNMAP_TWO, - UNMAP_THREE, - UNDO_START + UNMAP_THREE }; struct dev_dependent_vals { diff --git a/drivers/scsi/device_handler/scsi_dh_alua.c b/drivers/scsi/device_handler/scsi_dh_alua.c index 5bcdf8dd6fb0..8eaed0522aa3 100644 --- a/drivers/scsi/device_handler/scsi_dh_alua.c +++ b/drivers/scsi/device_handler/scsi_dh_alua.c @@ -332,7 +332,7 @@ static int alua_check_vpd(struct scsi_device *sdev, struct alua_dh_data *h, { int rel_port = -1, group_id; struct alua_port_group *pg, *old_pg = NULL; - bool pg_updated; + bool pg_updated = false; unsigned long flags; group_id = scsi_vpd_tpg_id(sdev, &rel_port); @@ -1112,9 +1112,9 @@ static void alua_bus_detach(struct scsi_device *sdev) h->sdev = NULL; spin_unlock(&h->pg_lock); if (pg) { - spin_lock(&pg->lock); + spin_lock_irq(&pg->lock); list_del_rcu(&h->node); - spin_unlock(&pg->lock); + spin_unlock_irq(&pg->lock); kref_put(&pg->kref, release_port_group); } sdev->handler_data = NULL; diff --git a/drivers/scsi/fcoe/fcoe_transport.c b/drivers/scsi/fcoe/fcoe_transport.c index d7597c08fa11..641c60e8fda3 100644 --- a/drivers/scsi/fcoe/fcoe_transport.c +++ b/drivers/scsi/fcoe/fcoe_transport.c @@ -93,36 +93,40 @@ static struct notifier_block libfcoe_notifier = { int fcoe_link_speed_update(struct fc_lport *lport) { struct net_device *netdev = fcoe_get_netdev(lport); - struct ethtool_cmd ecmd; + struct ethtool_link_ksettings ecmd; - if (!__ethtool_get_settings(netdev, &ecmd)) { + if (!__ethtool_get_link_ksettings(netdev, &ecmd)) { lport->link_supported_speeds &= ~(FC_PORTSPEED_1GBIT | FC_PORTSPEED_10GBIT | FC_PORTSPEED_20GBIT | FC_PORTSPEED_40GBIT); - if (ecmd.supported & (SUPPORTED_1000baseT_Half | - SUPPORTED_1000baseT_Full | - SUPPORTED_1000baseKX_Full)) + if (ecmd.link_modes.supported[0] & ( + SUPPORTED_1000baseT_Half | + SUPPORTED_1000baseT_Full | + SUPPORTED_1000baseKX_Full)) lport->link_supported_speeds |= FC_PORTSPEED_1GBIT; - if (ecmd.supported & (SUPPORTED_10000baseT_Full | - SUPPORTED_10000baseKX4_Full | - SUPPORTED_10000baseKR_Full | - SUPPORTED_10000baseR_FEC)) + if (ecmd.link_modes.supported[0] & ( + SUPPORTED_10000baseT_Full | + SUPPORTED_10000baseKX4_Full | + SUPPORTED_10000baseKR_Full | + SUPPORTED_10000baseR_FEC)) lport->link_supported_speeds |= FC_PORTSPEED_10GBIT; - if (ecmd.supported & (SUPPORTED_20000baseMLD2_Full | - SUPPORTED_20000baseKR2_Full)) + if (ecmd.link_modes.supported[0] & ( + SUPPORTED_20000baseMLD2_Full | + SUPPORTED_20000baseKR2_Full)) lport->link_supported_speeds |= FC_PORTSPEED_20GBIT; - if (ecmd.supported & (SUPPORTED_40000baseKR4_Full | - SUPPORTED_40000baseCR4_Full | - SUPPORTED_40000baseSR4_Full | - SUPPORTED_40000baseLR4_Full)) + if (ecmd.link_modes.supported[0] & ( + SUPPORTED_40000baseKR4_Full | + SUPPORTED_40000baseCR4_Full | + SUPPORTED_40000baseSR4_Full | + SUPPORTED_40000baseLR4_Full)) lport->link_supported_speeds |= FC_PORTSPEED_40GBIT; - switch (ethtool_cmd_speed(&ecmd)) { + switch (ecmd.base.speed) { case SPEED_1000: lport->link_speed = FC_PORTSPEED_1GBIT; break; diff --git a/drivers/scsi/fnic/fnic_scsi.c b/drivers/scsi/fnic/fnic_scsi.c index 266b909fe854..f3032ca5051b 100644 --- a/drivers/scsi/fnic/fnic_scsi.c +++ b/drivers/scsi/fnic/fnic_scsi.c @@ -958,23 +958,22 @@ static void fnic_fcpio_icmnd_cmpl_handler(struct fnic *fnic, case FCPIO_INVALID_PARAM: /* some parameter in request invalid */ case FCPIO_REQ_NOT_SUPPORTED:/* request type is not supported */ default: - shost_printk(KERN_ERR, fnic->lport->host, "hdr status = %s\n", - fnic_fcpio_status_to_str(hdr_status)); sc->result = (DID_ERROR << 16) | icmnd_cmpl->scsi_status; break; } - if (hdr_status != FCPIO_SUCCESS) { - atomic64_inc(&fnic_stats->io_stats.io_failures); - shost_printk(KERN_ERR, fnic->lport->host, "hdr status = %s\n", - fnic_fcpio_status_to_str(hdr_status)); - } /* Break link with the SCSI command */ CMD_SP(sc) = NULL; CMD_FLAGS(sc) |= FNIC_IO_DONE; spin_unlock_irqrestore(io_lock, flags); + if (hdr_status != FCPIO_SUCCESS) { + atomic64_inc(&fnic_stats->io_stats.io_failures); + shost_printk(KERN_ERR, fnic->lport->host, "hdr status = %s\n", + fnic_fcpio_status_to_str(hdr_status)); + } + fnic_release_ioreq_buf(fnic, io_req, sc); mempool_free(io_req, fnic->io_req_pool); diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c index 94025c5cf797..1547bd93c70b 100644 --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -250,6 +250,12 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, if (error) goto out_destroy_freelist; + /* + * Increase usage count temporarily here so that calling + * scsi_autopm_put_host() will trigger runtime idle if there is + * nothing else preventing suspending the device. + */ + pm_runtime_get_noresume(&shost->shost_gendev); pm_runtime_set_active(&shost->shost_gendev); pm_runtime_enable(&shost->shost_gendev); device_enable_async_suspend(&shost->shost_gendev); @@ -290,6 +296,7 @@ int scsi_add_host_with_dma(struct Scsi_Host *shost, struct device *dev, goto out_destroy_host; scsi_proc_host_add(shost); + scsi_autopm_put_host(shost); return error; out_destroy_host: diff --git a/drivers/scsi/lpfc/lpfc_init.c b/drivers/scsi/lpfc/lpfc_init.c index a544366a367e..f57d02c3b6cf 100644 --- a/drivers/scsi/lpfc/lpfc_init.c +++ b/drivers/scsi/lpfc/lpfc_init.c @@ -2860,7 +2860,7 @@ lpfc_online(struct lpfc_hba *phba) } vports = lpfc_create_vport_work_array(phba); - if (vports != NULL) + if (vports != NULL) { for (i = 0; i <= phba->max_vports && vports[i] != NULL; i++) { struct Scsi_Host *shost; shost = lpfc_shost_from_vport(vports[i]); @@ -2877,7 +2877,8 @@ lpfc_online(struct lpfc_hba *phba) } spin_unlock_irq(shost->host_lock); } - lpfc_destroy_vport_work_array(phba, vports); + } + lpfc_destroy_vport_work_array(phba, vports); lpfc_unblock_mgmt_io(phba); return 0; diff --git a/drivers/scsi/megaraid/megaraid_sas.h b/drivers/scsi/megaraid/megaraid_sas.h index 4484e63033a5..fce414a2cd76 100644 --- a/drivers/scsi/megaraid/megaraid_sas.h +++ b/drivers/scsi/megaraid/megaraid_sas.h @@ -2097,7 +2097,7 @@ struct megasas_instance { u8 UnevenSpanSupport; u8 supportmax256vd; - u8 allow_fw_scan; + u8 pd_list_not_supported; u16 fw_supported_vd_count; u16 fw_supported_pd_count; diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c index 5c08568ccfbf..e6ebc7ae2df1 100644 --- a/drivers/scsi/megaraid/megaraid_sas_base.c +++ b/drivers/scsi/megaraid/megaraid_sas_base.c @@ -1838,7 +1838,7 @@ static int megasas_slave_configure(struct scsi_device *sdev) struct megasas_instance *instance; instance = megasas_lookup_instance(sdev->host->host_no); - if (instance->allow_fw_scan) { + if (instance->pd_list_not_supported) { if (sdev->channel < MEGASAS_MAX_PD_CHANNELS && sdev->type == TYPE_DISK) { pd_index = (sdev->channel * MEGASAS_MAX_DEV_PER_CHANNEL) + @@ -1874,7 +1874,8 @@ static int megasas_slave_alloc(struct scsi_device *sdev) pd_index = (sdev->channel * MEGASAS_MAX_DEV_PER_CHANNEL) + sdev->id; - if ((instance->allow_fw_scan || instance->pd_list[pd_index].driveState == + if ((instance->pd_list_not_supported || + instance->pd_list[pd_index].driveState == MR_PD_STATE_SYSTEM)) { goto scan_target; } @@ -4087,7 +4088,13 @@ megasas_get_pd_list(struct megasas_instance *instance) switch (ret) { case DCMD_FAILED: - megaraid_sas_kill_hba(instance); + dev_info(&instance->pdev->dev, "MR_DCMD_PD_LIST_QUERY " + "failed/not supported by firmware\n"); + + if (instance->ctrl_context) + megaraid_sas_kill_hba(instance); + else + instance->pd_list_not_supported = 1; break; case DCMD_TIMEOUT: @@ -5034,7 +5041,6 @@ static int megasas_init_fw(struct megasas_instance *instance) case PCI_DEVICE_ID_DELL_PERC5: default: instance->instancet = &megasas_instance_template_xscale; - instance->allow_fw_scan = 1; break; } @@ -6650,12 +6656,13 @@ out: } for (i = 0; i < ioc->sge_count; i++) { - if (kbuff_arr[i]) + if (kbuff_arr[i]) { dma_free_coherent(&instance->pdev->dev, le32_to_cpu(kern_sge32[i].length), kbuff_arr[i], le32_to_cpu(kern_sge32[i].phys_addr)); kbuff_arr[i] = NULL; + } } megasas_return_cmd(instance, cmd); diff --git a/drivers/scsi/mpt3sas/mpt3sas_base.c b/drivers/scsi/mpt3sas/mpt3sas_base.c index e4db5fb3239a..8c44b9c424af 100644 --- a/drivers/scsi/mpt3sas/mpt3sas_base.c +++ b/drivers/scsi/mpt3sas/mpt3sas_base.c @@ -5030,7 +5030,7 @@ _base_make_ioc_ready(struct MPT3SAS_ADAPTER *ioc, int sleep_flag, static int _base_make_ioc_operational(struct MPT3SAS_ADAPTER *ioc, int sleep_flag) { - int r, i; + int r, i, index; unsigned long flags; u32 reply_address; u16 smid; @@ -5039,8 +5039,7 @@ _base_make_ioc_operational(struct MPT3SAS_ADAPTER *ioc, int sleep_flag) struct _event_ack_list *delayed_event_ack, *delayed_event_ack_next; u8 hide_flag; struct adapter_reply_queue *reply_q; - long reply_post_free; - u32 reply_post_free_sz, index = 0; + Mpi2ReplyDescriptorsUnion_t *reply_post_free_contig; dinitprintk(ioc, pr_info(MPT3SAS_FMT "%s\n", ioc->name, __func__)); @@ -5124,27 +5123,27 @@ _base_make_ioc_operational(struct MPT3SAS_ADAPTER *ioc, int sleep_flag) _base_assign_reply_queues(ioc); /* initialize Reply Post Free Queue */ - reply_post_free_sz = ioc->reply_post_queue_depth * - sizeof(Mpi2DefaultReplyDescriptor_t); - reply_post_free = (long)ioc->reply_post[index].reply_post_free; + index = 0; + reply_post_free_contig = ioc->reply_post[0].reply_post_free; list_for_each_entry(reply_q, &ioc->reply_queue_list, list) { + /* + * If RDPQ is enabled, switch to the next allocation. + * Otherwise advance within the contiguous region. + */ + if (ioc->rdpq_array_enable) { + reply_q->reply_post_free = + ioc->reply_post[index++].reply_post_free; + } else { + reply_q->reply_post_free = reply_post_free_contig; + reply_post_free_contig += ioc->reply_post_queue_depth; + } + reply_q->reply_post_host_index = 0; - reply_q->reply_post_free = (Mpi2ReplyDescriptorsUnion_t *) - reply_post_free; for (i = 0; i < ioc->reply_post_queue_depth; i++) reply_q->reply_post_free[i].Words = cpu_to_le64(ULLONG_MAX); if (!_base_is_controller_msix_enabled(ioc)) goto skip_init_reply_post_free_queue; - /* - * If RDPQ is enabled, switch to the next allocation. - * Otherwise advance within the contiguous region. - */ - if (ioc->rdpq_array_enable) - reply_post_free = (long) - ioc->reply_post[++index].reply_post_free; - else - reply_post_free += reply_post_free_sz; } skip_init_reply_post_free_queue: diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/qla_def.h index ceb452dd143c..47f8b9b49bac 100644 --- a/drivers/scsi/qla2xxx/qla_def.h +++ b/drivers/scsi/qla2xxx/qla_def.h @@ -2963,6 +2963,7 @@ struct qlt_hw_data { uint8_t tgt_node_name[WWN_SIZE]; + struct dentry *dfs_tgt_sess; struct list_head q_full_list; uint32_t num_pend_cmds; uint32_t num_qfull_cmds_alloc; diff --git a/drivers/scsi/qla2xxx/qla_dfs.c b/drivers/scsi/qla2xxx/qla_dfs.c index cd8b96a4b0dd..34272fde8a5b 100644 --- a/drivers/scsi/qla2xxx/qla_dfs.c +++ b/drivers/scsi/qla2xxx/qla_dfs.c @@ -13,6 +13,47 @@ static struct dentry *qla2x00_dfs_root; static atomic_t qla2x00_dfs_root_count; static int +qla2x00_dfs_tgt_sess_show(struct seq_file *s, void *unused) +{ + scsi_qla_host_t *vha = s->private; + struct qla_hw_data *ha = vha->hw; + unsigned long flags; + struct qla_tgt_sess *sess = NULL; + struct qla_tgt *tgt= vha->vha_tgt.qla_tgt; + + seq_printf(s, "%s\n",vha->host_str); + if (tgt) { + seq_printf(s, "Port ID Port Name Handle\n"); + + spin_lock_irqsave(&ha->tgt.sess_lock, flags); + list_for_each_entry(sess, &tgt->sess_list, sess_list_entry) { + seq_printf(s, "%02x:%02x:%02x %8phC %d\n", + sess->s_id.b.domain,sess->s_id.b.area, + sess->s_id.b.al_pa, sess->port_name, + sess->loop_id); + } + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); + } + + return 0; +} + +static int +qla2x00_dfs_tgt_sess_open(struct inode *inode, struct file *file) +{ + scsi_qla_host_t *vha = inode->i_private; + return single_open(file, qla2x00_dfs_tgt_sess_show, vha); +} + + +static const struct file_operations dfs_tgt_sess_ops = { + .open = qla2x00_dfs_tgt_sess_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int qla_dfs_fw_resource_cnt_show(struct seq_file *s, void *unused) { struct scsi_qla_host *vha = s->private; @@ -248,6 +289,15 @@ create_nodes: "Unable to create debugfs fce node.\n"); goto out; } + + ha->tgt.dfs_tgt_sess = debugfs_create_file("tgt_sess", + S_IRUSR, ha->dfs_dir, vha, &dfs_tgt_sess_ops); + if (!ha->tgt.dfs_tgt_sess) { + ql_log(ql_log_warn, vha, 0xffff, + "Unable to create debugFS tgt_sess node.\n"); + goto out; + } + out: return 0; } @@ -257,6 +307,11 @@ qla2x00_dfs_remove(scsi_qla_host_t *vha) { struct qla_hw_data *ha = vha->hw; + if (ha->tgt.dfs_tgt_sess) { + debugfs_remove(ha->tgt.dfs_tgt_sess); + ha->tgt.dfs_tgt_sess = NULL; + } + if (ha->dfs_fw_resource_cnt) { debugfs_remove(ha->dfs_fw_resource_cnt); ha->dfs_fw_resource_cnt = NULL; diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c index ee967becd257..8a44d1541eb4 100644 --- a/drivers/scsi/qla2xxx/qla_target.c +++ b/drivers/scsi/qla2xxx/qla_target.c @@ -641,7 +641,8 @@ void qlt_unreg_sess(struct qla_tgt_sess *sess) { struct scsi_qla_host *vha = sess->vha; - vha->hw->tgt.tgt_ops->clear_nacl_from_fcport_map(sess); + if (sess->se_sess) + vha->hw->tgt.tgt_ops->clear_nacl_from_fcport_map(sess); if (!list_empty(&sess->del_list_entry)) list_del_init(&sess->del_list_entry); @@ -856,8 +857,12 @@ static void qlt_del_sess_work_fn(struct delayed_work *work) ql_dbg(ql_dbg_tgt_mgt, vha, 0xf004, "Timeout: sess %p about to be deleted\n", sess); - ha->tgt.tgt_ops->shutdown_sess(sess); - ha->tgt.tgt_ops->put_sess(sess); + if (sess->se_sess) { + ha->tgt.tgt_ops->shutdown_sess(sess); + ha->tgt.tgt_ops->put_sess(sess); + } else { + qlt_unreg_sess(sess); + } } else { schedule_delayed_work(&tgt->sess_del_work, sess->expires - elapsed); @@ -879,7 +884,6 @@ static struct qla_tgt_sess *qlt_create_sess( struct qla_hw_data *ha = vha->hw; struct qla_tgt_sess *sess; unsigned long flags; - unsigned char be_sid[3]; /* Check to avoid double sessions */ spin_lock_irqsave(&ha->tgt.sess_lock, flags); @@ -905,6 +909,14 @@ static struct qla_tgt_sess *qlt_create_sess( if (sess->deleted) qlt_undelete_sess(sess); + if (!sess->se_sess) { + if (ha->tgt.tgt_ops->check_initiator_node_acl(vha, + &sess->port_name[0], sess) < 0) { + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); + return NULL; + } + } + kref_get(&sess->se_sess->sess_kref); ha->tgt.tgt_ops->update_sess(sess, fcport->d_id, fcport->loop_id, (fcport->flags & FCF_CONF_COMP_SUPPORTED)); @@ -948,26 +960,6 @@ static struct qla_tgt_sess *qlt_create_sess( "Adding sess %p to tgt %p via ->check_initiator_node_acl()\n", sess, vha->vha_tgt.qla_tgt); - be_sid[0] = sess->s_id.b.domain; - be_sid[1] = sess->s_id.b.area; - be_sid[2] = sess->s_id.b.al_pa; - /* - * Determine if this fc_port->port_name is allowed to access - * target mode using explict NodeACLs+MappedLUNs, or using - * TPG demo mode. If this is successful a target mode FC nexus - * is created. - */ - if (ha->tgt.tgt_ops->check_initiator_node_acl(vha, - &fcport->port_name[0], sess, &be_sid[0], fcport->loop_id) < 0) { - kfree(sess); - return NULL; - } - /* - * Take an extra reference to ->sess_kref here to handle qla_tgt_sess - * access across ->tgt.sess_lock reaquire. - */ - kref_get(&sess->se_sess->sess_kref); - sess->conf_compl_supported = (fcport->flags & FCF_CONF_COMP_SUPPORTED); BUILD_BUG_ON(sizeof(sess->port_name) != sizeof(fcport->port_name)); memcpy(sess->port_name, fcport->port_name, sizeof(sess->port_name)); @@ -985,6 +977,23 @@ static struct qla_tgt_sess *qlt_create_sess( fcport->loop_id, sess->s_id.b.domain, sess->s_id.b.area, sess->s_id.b.al_pa, sess->conf_compl_supported ? "" : "not "); + /* + * Determine if this fc_port->port_name is allowed to access + * target mode using explict NodeACLs+MappedLUNs, or using + * TPG demo mode. If this is successful a target mode FC nexus + * is created. + */ + if (ha->tgt.tgt_ops->check_initiator_node_acl(vha, + &fcport->port_name[0], sess) < 0) { + return NULL; + } else { + /* + * Take an extra reference to ->sess_kref here to handle qla_tgt_sess + * access across ->tgt.sess_lock reaquire. + */ + kref_get(&sess->se_sess->sess_kref); + } + return sess; } @@ -1872,15 +1881,17 @@ static int qlt_check_reserve_free_req(struct scsi_qla_host *vha, else vha->req->cnt = vha->req->length - (vha->req->ring_index - cnt); - } - if (unlikely(vha->req->cnt < (req_cnt + 2))) { - ql_dbg(ql_dbg_io, vha, 0x305a, - "qla_target(%d): There is no room in the request ring: vha->req->ring_index=%d, vha->req->cnt=%d, req_cnt=%d Req-out=%d Req-in=%d Req-Length=%d\n", - vha->vp_idx, vha->req->ring_index, - vha->req->cnt, req_cnt, cnt, cnt_in, vha->req->length); - return -EAGAIN; + if (unlikely(vha->req->cnt < (req_cnt + 2))) { + ql_dbg(ql_dbg_io, vha, 0x305a, + "qla_target(%d): There is no room in the request ring: vha->req->ring_index=%d, vha->req->cnt=%d, req_cnt=%d Req-out=%d Req-in=%d Req-Length=%d\n", + vha->vp_idx, vha->req->ring_index, + vha->req->cnt, req_cnt, cnt, cnt_in, + vha->req->length); + return -EAGAIN; + } } + vha->req->cnt -= req_cnt; return 0; diff --git a/drivers/scsi/qla2xxx/qla_target.h b/drivers/scsi/qla2xxx/qla_target.h index 22a6a767fe07..d857feeb6514 100644 --- a/drivers/scsi/qla2xxx/qla_target.h +++ b/drivers/scsi/qla2xxx/qla_target.h @@ -731,7 +731,7 @@ struct qla_tgt_func_tmpl { void (*free_session)(struct qla_tgt_sess *); int (*check_initiator_node_acl)(struct scsi_qla_host *, unsigned char *, - void *, uint8_t *, uint16_t); + struct qla_tgt_sess *); void (*update_sess)(struct qla_tgt_sess *, port_id_t, uint16_t, bool); struct qla_tgt_sess *(*find_sess_by_loop_id)(struct scsi_qla_host *, const uint16_t); diff --git a/drivers/scsi/qla2xxx/tcm_qla2xxx.c b/drivers/scsi/qla2xxx/tcm_qla2xxx.c index 1808a01cfb7e..c1461d225f08 100644 --- a/drivers/scsi/qla2xxx/tcm_qla2xxx.c +++ b/drivers/scsi/qla2xxx/tcm_qla2xxx.c @@ -1406,6 +1406,39 @@ static void tcm_qla2xxx_free_session(struct qla_tgt_sess *sess) transport_deregister_session(sess->se_sess); } +static int tcm_qla2xxx_session_cb(struct se_portal_group *se_tpg, + struct se_session *se_sess, void *p) +{ + struct tcm_qla2xxx_tpg *tpg = container_of(se_tpg, + struct tcm_qla2xxx_tpg, se_tpg); + struct tcm_qla2xxx_lport *lport = tpg->lport; + struct qla_hw_data *ha = lport->qla_vha->hw; + struct se_node_acl *se_nacl = se_sess->se_node_acl; + struct tcm_qla2xxx_nacl *nacl = container_of(se_nacl, + struct tcm_qla2xxx_nacl, se_node_acl); + struct qla_tgt_sess *qlat_sess = p; + uint16_t loop_id = qlat_sess->loop_id; + unsigned long flags; + unsigned char be_sid[3]; + + be_sid[0] = qlat_sess->s_id.b.domain; + be_sid[1] = qlat_sess->s_id.b.area; + be_sid[2] = qlat_sess->s_id.b.al_pa; + + /* + * And now setup se_nacl and session pointers into HW lport internal + * mappings for fabric S_ID and LOOP_ID. + */ + spin_lock_irqsave(&ha->tgt.sess_lock, flags); + tcm_qla2xxx_set_sess_by_s_id(lport, se_nacl, nacl, + se_sess, qlat_sess, be_sid); + tcm_qla2xxx_set_sess_by_loop_id(lport, se_nacl, nacl, + se_sess, qlat_sess, loop_id); + spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); + + return 0; +} + /* * Called via qlt_create_sess():ha->qla2x_tmpl->check_initiator_node_acl() * to locate struct se_node_acl @@ -1413,20 +1446,13 @@ static void tcm_qla2xxx_free_session(struct qla_tgt_sess *sess) static int tcm_qla2xxx_check_initiator_node_acl( scsi_qla_host_t *vha, unsigned char *fc_wwpn, - void *qla_tgt_sess, - uint8_t *s_id, - uint16_t loop_id) + struct qla_tgt_sess *qlat_sess) { struct qla_hw_data *ha = vha->hw; struct tcm_qla2xxx_lport *lport; struct tcm_qla2xxx_tpg *tpg; - struct tcm_qla2xxx_nacl *nacl; - struct se_portal_group *se_tpg; - struct se_node_acl *se_nacl; struct se_session *se_sess; - struct qla_tgt_sess *sess = qla_tgt_sess; unsigned char port_name[36]; - unsigned long flags; int num_tags = (ha->cur_fw_xcb_count) ? ha->cur_fw_xcb_count : TCM_QLA2XXX_DEFAULT_TAGS; @@ -1444,15 +1470,6 @@ static int tcm_qla2xxx_check_initiator_node_acl( pr_err("Unable to lcoate struct tcm_qla2xxx_lport->tpg_1\n"); return -EINVAL; } - se_tpg = &tpg->se_tpg; - - se_sess = transport_init_session_tags(num_tags, - sizeof(struct qla_tgt_cmd), - TARGET_PROT_ALL); - if (IS_ERR(se_sess)) { - pr_err("Unable to initialize struct se_session\n"); - return PTR_ERR(se_sess); - } /* * Format the FCP Initiator port_name into colon seperated values to * match the format by tcm_qla2xxx explict ConfigFS NodeACLs. @@ -1463,28 +1480,12 @@ static int tcm_qla2xxx_check_initiator_node_acl( * Locate our struct se_node_acl either from an explict NodeACL created * via ConfigFS, or via running in TPG demo mode. */ - se_sess->se_node_acl = core_tpg_check_initiator_node_acl(se_tpg, - port_name); - if (!se_sess->se_node_acl) { - transport_free_session(se_sess); - return -EINVAL; - } - se_nacl = se_sess->se_node_acl; - nacl = container_of(se_nacl, struct tcm_qla2xxx_nacl, se_node_acl); - /* - * And now setup the new se_nacl and session pointers into our HW lport - * mappings for fabric S_ID and LOOP_ID. - */ - spin_lock_irqsave(&ha->tgt.sess_lock, flags); - tcm_qla2xxx_set_sess_by_s_id(lport, se_nacl, nacl, se_sess, - qla_tgt_sess, s_id); - tcm_qla2xxx_set_sess_by_loop_id(lport, se_nacl, nacl, se_sess, - qla_tgt_sess, loop_id); - spin_unlock_irqrestore(&ha->tgt.sess_lock, flags); - /* - * Finally register the new FC Nexus with TCM - */ - transport_register_session(se_nacl->se_tpg, se_nacl, se_sess, sess); + se_sess = target_alloc_session(&tpg->se_tpg, num_tags, + sizeof(struct qla_tgt_cmd), + TARGET_PROT_ALL, port_name, + qlat_sess, tcm_qla2xxx_session_cb); + if (IS_ERR(se_sess)) + return PTR_ERR(se_sess); return 0; } diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c index b1bf42b93fcc..1deb6adc411f 100644 --- a/drivers/scsi/scsi.c +++ b/drivers/scsi/scsi.c @@ -784,8 +784,9 @@ void scsi_attach_vpd(struct scsi_device *sdev) int pg83_supported = 0; unsigned char __rcu *vpd_buf, *orig_vpd_buf = NULL; - if (sdev->skip_vpd_pages) + if (!scsi_device_supports_vpd(sdev)) return; + retry_pg0: vpd_buf = kmalloc(vpd_len, GFP_KERNEL); if (!vpd_buf) diff --git a/drivers/scsi/scsi_common.c b/drivers/scsi/scsi_common.c index c126966130ab..ce79de822e46 100644 --- a/drivers/scsi/scsi_common.c +++ b/drivers/scsi/scsi_common.c @@ -278,8 +278,16 @@ int scsi_set_sense_information(u8 *buf, int buf_len, u64 info) ucp[3] = 0; put_unaligned_be64(info, &ucp[4]); } else if ((buf[0] & 0x7f) == 0x70) { - buf[0] |= 0x80; - put_unaligned_be64(info, &buf[3]); + /* + * Only set the 'VALID' bit if we can represent the value + * correctly; otherwise just fill out the lower bytes and + * clear the 'VALID' flag. + */ + if (info <= 0xffffffffUL) + buf[0] |= 0x80; + else + buf[0] &= 0x7f; + put_unaligned_be32((u32)info, &buf[3]); } return 0; diff --git a/drivers/scsi/scsi_pm.c b/drivers/scsi/scsi_pm.c index 459abe1dcc87..b44c1bb687a2 100644 --- a/drivers/scsi/scsi_pm.c +++ b/drivers/scsi/scsi_pm.c @@ -139,6 +139,16 @@ static int scsi_bus_resume_common(struct device *dev, else fn = NULL; + /* + * Forcibly set runtime PM status of request queue to "active" to + * make sure we can again get requests from the queue (see also + * blk_pm_peek_request()). + * + * The resume hook will correct runtime PM status of the disk. + */ + if (scsi_is_sdev_device(dev) && pm_runtime_suspended(dev)) + blk_set_runtime_active(to_scsi_device(dev)->request_queue); + if (fn) { async_schedule_domain(fn, dev, &scsi_sd_pm_domain); diff --git a/drivers/scsi/scsi_sas_internal.h b/drivers/scsi/scsi_sas_internal.h index 6266a5d73d0f..e659912498bd 100644 --- a/drivers/scsi/scsi_sas_internal.h +++ b/drivers/scsi/scsi_sas_internal.h @@ -4,7 +4,7 @@ #define SAS_HOST_ATTRS 0 #define SAS_PHY_ATTRS 17 #define SAS_PORT_ATTRS 1 -#define SAS_RPORT_ATTRS 7 +#define SAS_RPORT_ATTRS 8 #define SAS_END_DEV_ATTRS 5 #define SAS_EXPANDER_ATTRS 7 diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index d16441961f3a..2b642b145be1 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -81,6 +81,7 @@ const char *scsi_host_state_name(enum scsi_host_state state) return name; } +#ifdef CONFIG_SCSI_DH static const struct { unsigned char value; char *name; @@ -94,7 +95,7 @@ static const struct { { SCSI_ACCESS_STATE_TRANSITIONING, "transitioning" }, }; -const char *scsi_access_state_name(unsigned char state) +static const char *scsi_access_state_name(unsigned char state) { int i; char *name = NULL; @@ -107,6 +108,7 @@ const char *scsi_access_state_name(unsigned char state) } return name; } +#endif static int check_set(unsigned long long *val, char *src) { @@ -226,7 +228,7 @@ show_shost_state(struct device *dev, struct device_attribute *attr, char *buf) } /* DEVICE_ATTR(state) clashes with dev_attr_state for sdev */ -struct device_attribute dev_attr_hstate = +static struct device_attribute dev_attr_hstate = __ATTR(state, S_IRUGO | S_IWUSR, show_shost_state, store_shost_state); static ssize_t @@ -401,7 +403,7 @@ static struct attribute *scsi_sysfs_shost_attrs[] = { NULL }; -struct attribute_group scsi_shost_attr_group = { +static struct attribute_group scsi_shost_attr_group = { .attrs = scsi_sysfs_shost_attrs, }; @@ -1105,7 +1107,7 @@ static umode_t scsi_sdev_bin_attr_is_visible(struct kobject *kobj, if (attr == &dev_attr_vpd_pg80 && !sdev->vpd_pg80) return 0; - if (attr == &dev_attr_vpd_pg83 && sdev->vpd_pg83) + if (attr == &dev_attr_vpd_pg83 && !sdev->vpd_pg83) return 0; return S_IRUGO; diff --git a/drivers/scsi/scsi_transport_sas.c b/drivers/scsi/scsi_transport_sas.c index 80520e2f0fa2..b6f958193dad 100644 --- a/drivers/scsi/scsi_transport_sas.c +++ b/drivers/scsi/scsi_transport_sas.c @@ -1286,6 +1286,7 @@ sas_rphy_protocol_attr(identify.target_port_protocols, target_port_protocols); sas_rphy_simple_attr(identify.sas_address, sas_address, "0x%016llx\n", unsigned long long); sas_rphy_simple_attr(identify.phy_identifier, phy_identifier, "%d\n", u8); +sas_rphy_simple_attr(scsi_target_id, scsi_target_id, "%d\n", u32); /* only need 8 bytes of data plus header (4 or 8) */ #define BUF_SIZE 64 @@ -1886,6 +1887,7 @@ sas_attach_transport(struct sas_function_template *ft) SETUP_RPORT_ATTRIBUTE(rphy_device_type); SETUP_RPORT_ATTRIBUTE(rphy_sas_address); SETUP_RPORT_ATTRIBUTE(rphy_phy_identifier); + SETUP_RPORT_ATTRIBUTE(rphy_scsi_target_id); SETUP_OPTIONAL_RPORT_ATTRIBUTE(rphy_enclosure_identifier, get_enclosure_identifier); SETUP_OPTIONAL_RPORT_ATTRIBUTE(rphy_bay_identifier, diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 5a5457ac9cdb..f52b74cf8d1e 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -1275,18 +1275,19 @@ static int sd_getgeo(struct block_device *bdev, struct hd_geometry *geo) struct scsi_disk *sdkp = scsi_disk(bdev->bd_disk); struct scsi_device *sdp = sdkp->device; struct Scsi_Host *host = sdp->host; + sector_t capacity = logical_to_sectors(sdp, sdkp->capacity); int diskinfo[4]; /* default to most commonly used values */ - diskinfo[0] = 0x40; /* 1 << 6 */ - diskinfo[1] = 0x20; /* 1 << 5 */ - diskinfo[2] = sdkp->capacity >> 11; - + diskinfo[0] = 0x40; /* 1 << 6 */ + diskinfo[1] = 0x20; /* 1 << 5 */ + diskinfo[2] = capacity >> 11; + /* override with calculated, extended default, or driver values */ if (host->hostt->bios_param) - host->hostt->bios_param(sdp, bdev, sdkp->capacity, diskinfo); + host->hostt->bios_param(sdp, bdev, capacity, diskinfo); else - scsicam_bios_param(bdev, sdkp->capacity, diskinfo); + scsicam_bios_param(bdev, capacity, diskinfo); geo->heads = diskinfo[0]; geo->sectors = diskinfo[1]; @@ -2337,14 +2338,6 @@ got_data: if (sdkp->capacity > 0xffffffff) sdp->use_16_for_rw = 1; - /* Rescale capacity to 512-byte units */ - if (sector_size == 4096) - sdkp->capacity <<= 3; - else if (sector_size == 2048) - sdkp->capacity <<= 2; - else if (sector_size == 1024) - sdkp->capacity <<= 1; - blk_queue_physical_block_size(sdp->request_queue, sdkp->physical_block_size); sdkp->device->sector_size = sector_size; @@ -2795,28 +2788,6 @@ static void sd_read_write_same(struct scsi_disk *sdkp, unsigned char *buffer) sdkp->ws10 = 1; } -static int sd_try_extended_inquiry(struct scsi_device *sdp) -{ - /* Attempt VPD inquiry if the device blacklist explicitly calls - * for it. - */ - if (sdp->try_vpd_pages) - return 1; - /* - * Although VPD inquiries can go to SCSI-2 type devices, - * some USB ones crash on receiving them, and the pages - * we currently ask for are for SPC-3 and beyond - */ - if (sdp->scsi_level > SCSI_SPC_2 && !sdp->skip_vpd_pages) - return 1; - return 0; -} - -static inline u32 logical_to_sectors(struct scsi_device *sdev, u32 blocks) -{ - return blocks << (ilog2(sdev->sector_size) - 9); -} - /** * sd_revalidate_disk - called the first time a new disk is seen, * performs disk spin up, read_capacity, etc. @@ -2856,7 +2827,7 @@ static int sd_revalidate_disk(struct gendisk *disk) if (sdkp->media_present) { sd_read_capacity(sdkp, buffer); - if (sd_try_extended_inquiry(sdp)) { + if (scsi_device_supports_vpd(sdp)) { sd_read_block_provisioning(sdkp); sd_read_block_limits(sdkp); sd_read_block_characteristics(sdkp); @@ -2891,7 +2862,7 @@ static int sd_revalidate_disk(struct gendisk *disk) if (sdkp->opt_xfer_blocks && sdkp->opt_xfer_blocks <= dev_max && sdkp->opt_xfer_blocks <= SD_DEF_XFER_BLOCKS && - sdkp->opt_xfer_blocks * sdp->sector_size >= PAGE_CACHE_SIZE) + sdkp->opt_xfer_blocks * sdp->sector_size >= PAGE_SIZE) rw_max = q->limits.io_opt = sdkp->opt_xfer_blocks * sdp->sector_size; else @@ -2900,7 +2871,7 @@ static int sd_revalidate_disk(struct gendisk *disk) /* Combine with controller limits */ q->limits.max_sectors = min(rw_max, queue_max_hw_sectors(q)); - set_capacity(disk, sdkp->capacity); + set_capacity(disk, logical_to_sectors(sdp, sdkp->capacity)); sd_config_write_same(sdkp); kfree(buffer); diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h index 5f2a84aff29f..654630bb7d0e 100644 --- a/drivers/scsi/sd.h +++ b/drivers/scsi/sd.h @@ -65,7 +65,7 @@ struct scsi_disk { struct device dev; struct gendisk *disk; atomic_t openers; - sector_t capacity; /* size in 512-byte sectors */ + sector_t capacity; /* size in logical blocks */ u32 max_xfer_blocks; u32 opt_xfer_blocks; u32 max_ws_blocks; @@ -146,6 +146,11 @@ static inline int scsi_medium_access_command(struct scsi_cmnd *scmd) return 0; } +static inline sector_t logical_to_sectors(struct scsi_device *sdev, sector_t blocks) +{ + return blocks << (ilog2(sdev->sector_size) - 9); +} + /* * A DIF-capable target device can be formatted with different * protection schemes. Currently 0 through 3 are defined: diff --git a/drivers/scsi/st.c b/drivers/scsi/st.c index 607b0a505844..dbf1882cfbac 100644 --- a/drivers/scsi/st.c +++ b/drivers/scsi/st.c @@ -4917,8 +4917,6 @@ static int sgl_map_user_pages(struct st_buffer *STbp, /* Try to fault in all of the necessary pages */ /* rw==READ means read from drive, write into memory area */ res = get_user_pages_unlocked( - current, - current->mm, uaddr, nr_pages, rw == READ, @@ -4943,7 +4941,7 @@ static int sgl_map_user_pages(struct st_buffer *STbp, out_unmap: if (res > 0) { for (j=0; j < res; j++) - page_cache_release(pages[j]); + put_page(pages[j]); res = 0; } kfree(pages); @@ -4965,7 +4963,7 @@ static int sgl_unmap_user_pages(struct st_buffer *STbp, /* FIXME: cache flush missing for rw==READ * FIXME: call the correct reference counting function */ - page_cache_release(page); + put_page(page); } kfree(STbp->mapped_pages); STbp->mapped_pages = NULL; diff --git a/drivers/scsi/ufs/Kconfig b/drivers/scsi/ufs/Kconfig index 5f4530744e0a..097894a1fab5 100644 --- a/drivers/scsi/ufs/Kconfig +++ b/drivers/scsi/ufs/Kconfig @@ -37,6 +37,7 @@ config SCSI_UFSHCD depends on SCSI && SCSI_DMA select PM_DEVFREQ select DEVFREQ_GOV_SIMPLE_ONDEMAND + select NLS ---help--- This selects the support for UFS devices in Linux, say Y and make sure that you know the name of your UFS host adapter (the card diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index 4f38d008bfb4..3aedf73f1131 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2015, Linux Foundation. All rights reserved. + * Copyright (c) 2013-2016, Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -16,8 +16,8 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/phy/phy.h> - #include <linux/phy/phy-qcom-ufs.h> + #include "ufshcd.h" #include "ufshcd-pltfrm.h" #include "unipro.h" @@ -58,6 +58,12 @@ static void ufs_qcom_dump_regs(struct ufs_hba *hba, int offset, int len, len * 4, false); } +static void ufs_qcom_dump_regs_wrapper(struct ufs_hba *hba, int offset, int len, + char *prefix, void *priv) +{ + ufs_qcom_dump_regs(hba, offset, len, prefix); +} + static int ufs_qcom_get_connected_tx_lanes(struct ufs_hba *hba, u32 *tx_lanes) { int err = 0; @@ -106,9 +112,11 @@ static void ufs_qcom_disable_lane_clks(struct ufs_qcom_host *host) if (!host->is_lane_clks_enabled) return; - clk_disable_unprepare(host->tx_l1_sync_clk); + if (host->hba->lanes_per_direction > 1) + clk_disable_unprepare(host->tx_l1_sync_clk); clk_disable_unprepare(host->tx_l0_sync_clk); - clk_disable_unprepare(host->rx_l1_sync_clk); + if (host->hba->lanes_per_direction > 1) + clk_disable_unprepare(host->rx_l1_sync_clk); clk_disable_unprepare(host->rx_l0_sync_clk); host->is_lane_clks_enabled = false; @@ -132,21 +140,24 @@ static int ufs_qcom_enable_lane_clks(struct ufs_qcom_host *host) if (err) goto disable_rx_l0; - err = ufs_qcom_host_clk_enable(dev, "rx_lane1_sync_clk", - host->rx_l1_sync_clk); - if (err) - goto disable_tx_l0; + if (host->hba->lanes_per_direction > 1) { + err = ufs_qcom_host_clk_enable(dev, "rx_lane1_sync_clk", + host->rx_l1_sync_clk); + if (err) + goto disable_tx_l0; - err = ufs_qcom_host_clk_enable(dev, "tx_lane1_sync_clk", - host->tx_l1_sync_clk); - if (err) - goto disable_rx_l1; + err = ufs_qcom_host_clk_enable(dev, "tx_lane1_sync_clk", + host->tx_l1_sync_clk); + if (err) + goto disable_rx_l1; + } host->is_lane_clks_enabled = true; goto out; disable_rx_l1: - clk_disable_unprepare(host->rx_l1_sync_clk); + if (host->hba->lanes_per_direction > 1) + clk_disable_unprepare(host->rx_l1_sync_clk); disable_tx_l0: clk_disable_unprepare(host->tx_l0_sync_clk); disable_rx_l0: @@ -170,14 +181,16 @@ static int ufs_qcom_init_lane_clks(struct ufs_qcom_host *host) if (err) goto out; - err = ufs_qcom_host_clk_get(dev, "rx_lane1_sync_clk", - &host->rx_l1_sync_clk); - if (err) - goto out; - - err = ufs_qcom_host_clk_get(dev, "tx_lane1_sync_clk", - &host->tx_l1_sync_clk); + /* In case of single lane per direction, don't read lane1 clocks */ + if (host->hba->lanes_per_direction > 1) { + err = ufs_qcom_host_clk_get(dev, "rx_lane1_sync_clk", + &host->rx_l1_sync_clk); + if (err) + goto out; + err = ufs_qcom_host_clk_get(dev, "tx_lane1_sync_clk", + &host->tx_l1_sync_clk); + } out: return err; } @@ -267,9 +280,8 @@ static int ufs_qcom_power_up_sequence(struct ufs_hba *hba) ret = ufs_qcom_phy_calibrate_phy(phy, is_rate_B); if (ret) { - dev_err(hba->dev, - "%s: ufs_qcom_phy_calibrate_phy()failed, ret = %d\n", - __func__, ret); + dev_err(hba->dev, "%s: ufs_qcom_phy_calibrate_phy() failed, ret = %d\n", + __func__, ret); goto out; } @@ -519,6 +531,18 @@ static int ufs_qcom_link_startup_notify(struct ufs_hba *hba, err = ufs_qcom_set_dme_vs_core_clk_ctrl_clear_div(hba, 150); + /* + * Some UFS devices (and may be host) have issues if LCC is + * enabled. So we are setting PA_Local_TX_LCC_Enable to 0 + * before link startup which will make sure that both host + * and device TX LCC are disabled once link startup is + * completed. + */ + if (ufshcd_get_local_unipro_ver(hba) != UFS_UNIPRO_VER_1_41) + err = ufshcd_dme_set(hba, + UIC_ARG_MIB(PA_LOCAL_TX_LCC_ENABLE), + 0); + break; case POST_CHANGE: ufs_qcom_link_startup_post_change(hba); @@ -962,6 +986,10 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba, goto out; } + /* enable the device ref clock before changing to HS mode */ + if (!ufshcd_is_hs_mode(&hba->pwr_info) && + ufshcd_is_hs_mode(dev_req_params)) + ufs_qcom_dev_ref_clk_ctrl(host, true); break; case POST_CHANGE: if (ufs_qcom_cfg_timers(hba, dev_req_params->gear_rx, @@ -989,6 +1017,11 @@ static int ufs_qcom_pwr_change_notify(struct ufs_hba *hba, memcpy(&host->dev_req_params, dev_req_params, sizeof(*dev_req_params)); ufs_qcom_update_bus_bw_vote(host); + + /* disable the device ref clock if entered PWM mode */ + if (ufshcd_is_hs_mode(&hba->pwr_info) && + !ufshcd_is_hs_mode(dev_req_params)) + ufs_qcom_dev_ref_clk_ctrl(host, false); break; default: ret = -EINVAL; @@ -1090,6 +1123,9 @@ static int ufs_qcom_setup_clocks(struct ufs_hba *hba, bool on) ufs_qcom_phy_disable_iface_clk(host->generic_phy); goto out; } + /* enable the device ref clock for HS mode*/ + if (ufshcd_is_hs_mode(&hba->pwr_info)) + ufs_qcom_dev_ref_clk_ctrl(host, true); vote = host->bus_vote.saved_vote; if (vote == host->bus_vote.min_bw_vote) ufs_qcom_update_bus_bw_vote(host); @@ -1367,6 +1403,74 @@ out: return err; } +static void ufs_qcom_print_hw_debug_reg_all(struct ufs_hba *hba, + void *priv, void (*print_fn)(struct ufs_hba *hba, + int offset, int num_regs, char *str, void *priv)) +{ + u32 reg; + struct ufs_qcom_host *host; + + if (unlikely(!hba)) { + pr_err("%s: hba is NULL\n", __func__); + return; + } + if (unlikely(!print_fn)) { + dev_err(hba->dev, "%s: print_fn is NULL\n", __func__); + return; + } + + host = ufshcd_get_variant(hba); + if (!(host->dbg_print_en & UFS_QCOM_DBG_PRINT_REGS_EN)) + return; + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_REG_OCSC); + print_fn(hba, reg, 44, "UFS_UFS_DBG_RD_REG_OCSC ", priv); + + reg = ufshcd_readl(hba, REG_UFS_CFG1); + reg |= UFS_BIT(17); + ufshcd_writel(hba, reg, REG_UFS_CFG1); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_EDTL_RAM); + print_fn(hba, reg, 32, "UFS_UFS_DBG_RD_EDTL_RAM ", priv); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_DESC_RAM); + print_fn(hba, reg, 128, "UFS_UFS_DBG_RD_DESC_RAM ", priv); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_UFS_DBG_RD_PRDT_RAM); + print_fn(hba, reg, 64, "UFS_UFS_DBG_RD_PRDT_RAM ", priv); + + ufshcd_writel(hba, (reg & ~UFS_BIT(17)), REG_UFS_CFG1); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_UAWM); + print_fn(hba, reg, 4, "UFS_DBG_RD_REG_UAWM ", priv); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_UARM); + print_fn(hba, reg, 4, "UFS_DBG_RD_REG_UARM ", priv); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TXUC); + print_fn(hba, reg, 48, "UFS_DBG_RD_REG_TXUC ", priv); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_RXUC); + print_fn(hba, reg, 27, "UFS_DBG_RD_REG_RXUC ", priv); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_DFC); + print_fn(hba, reg, 19, "UFS_DBG_RD_REG_DFC ", priv); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TRLUT); + print_fn(hba, reg, 34, "UFS_DBG_RD_REG_TRLUT ", priv); + + reg = ufs_qcom_get_debug_reg_offset(host, UFS_DBG_RD_REG_TMRLUT); + print_fn(hba, reg, 9, "UFS_DBG_RD_REG_TMRLUT ", priv); +} + +static void ufs_qcom_enable_test_bus(struct ufs_qcom_host *host) +{ + if (host->dbg_print_en & UFS_QCOM_DBG_PRINT_TEST_BUS_EN) + ufshcd_rmwl(host->hba, TEST_BUS_EN, TEST_BUS_EN, REG_UFS_CFG1); + else + ufshcd_rmwl(host->hba, TEST_BUS_EN, 0, REG_UFS_CFG1); +} + static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host) { /* provide a legal default configuration */ @@ -1475,6 +1579,7 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host) ufshcd_rmwl(host->hba, mask, (u32)host->testbus.select_minor << offset, reg); + ufs_qcom_enable_test_bus(host); ufshcd_release(host->hba); pm_runtime_put_sync(host->hba->dev); @@ -1491,8 +1596,10 @@ static void ufs_qcom_dump_dbg_regs(struct ufs_hba *hba) ufs_qcom_dump_regs(hba, REG_UFS_SYS1CLK_1US, 16, "HCI Vendor Specific Registers "); + ufs_qcom_print_hw_debug_reg_all(hba, NULL, ufs_qcom_dump_regs_wrapper); ufs_qcom_testbus_read(hba); } + /** * struct ufs_hba_qcom_vops - UFS QCOM specific variant operations * @@ -1537,7 +1644,7 @@ static int ufs_qcom_probe(struct platform_device *pdev) * ufs_qcom_remove - set driver_data of the device to NULL * @pdev: pointer to platform device handle * - * Always return 0 + * Always returns 0 */ static int ufs_qcom_remove(struct platform_device *pdev) { diff --git a/drivers/scsi/ufs/ufs-qcom.h b/drivers/scsi/ufs/ufs-qcom.h index 36249b35f858..a19307a57ce2 100644 --- a/drivers/scsi/ufs/ufs-qcom.h +++ b/drivers/scsi/ufs/ufs-qcom.h @@ -241,6 +241,15 @@ struct ufs_qcom_host { struct ufs_qcom_testbus testbus; }; +static inline u32 +ufs_qcom_get_debug_reg_offset(struct ufs_qcom_host *host, u32 reg) +{ + if (host->hw_ver.major <= 0x02) + return UFS_CNTLR_2_x_x_VEN_REGS_OFFSET(reg); + + return UFS_CNTLR_3_x_x_VEN_REGS_OFFSET(reg); +}; + #define ufs_qcom_is_link_off(hba) ufshcd_is_link_off(hba) #define ufs_qcom_is_link_active(hba) ufshcd_is_link_active(hba) #define ufs_qcom_is_link_hibern8(hba) ufshcd_is_link_hibern8(hba) diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h index 54a16cef0367..b291fa6ed2ad 100644 --- a/drivers/scsi/ufs/ufs.h +++ b/drivers/scsi/ufs/ufs.h @@ -43,6 +43,7 @@ #define GENERAL_UPIU_REQUEST_SIZE 32 #define QUERY_DESC_MAX_SIZE 255 #define QUERY_DESC_MIN_SIZE 2 +#define QUERY_DESC_HDR_SIZE 2 #define QUERY_OSF_SIZE (GENERAL_UPIU_REQUEST_SIZE - \ (sizeof(struct utp_upiu_header))) @@ -195,6 +196,37 @@ enum unit_desc_param { UNIT_DESC_PARAM_LARGE_UNIT_SIZE_M1 = 0x22, }; +/* Device descriptor parameters offsets in bytes*/ +enum device_desc_param { + DEVICE_DESC_PARAM_LEN = 0x0, + DEVICE_DESC_PARAM_TYPE = 0x1, + DEVICE_DESC_PARAM_DEVICE_TYPE = 0x2, + DEVICE_DESC_PARAM_DEVICE_CLASS = 0x3, + DEVICE_DESC_PARAM_DEVICE_SUB_CLASS = 0x4, + DEVICE_DESC_PARAM_PRTCL = 0x5, + DEVICE_DESC_PARAM_NUM_LU = 0x6, + DEVICE_DESC_PARAM_NUM_WLU = 0x7, + DEVICE_DESC_PARAM_BOOT_ENBL = 0x8, + DEVICE_DESC_PARAM_DESC_ACCSS_ENBL = 0x9, + DEVICE_DESC_PARAM_INIT_PWR_MODE = 0xA, + DEVICE_DESC_PARAM_HIGH_PR_LUN = 0xB, + DEVICE_DESC_PARAM_SEC_RMV_TYPE = 0xC, + DEVICE_DESC_PARAM_SEC_LU = 0xD, + DEVICE_DESC_PARAM_BKOP_TERM_LT = 0xE, + DEVICE_DESC_PARAM_ACTVE_ICC_LVL = 0xF, + DEVICE_DESC_PARAM_SPEC_VER = 0x10, + DEVICE_DESC_PARAM_MANF_DATE = 0x12, + DEVICE_DESC_PARAM_MANF_NAME = 0x14, + DEVICE_DESC_PARAM_PRDCT_NAME = 0x15, + DEVICE_DESC_PARAM_SN = 0x16, + DEVICE_DESC_PARAM_OEM_ID = 0x17, + DEVICE_DESC_PARAM_MANF_ID = 0x18, + DEVICE_DESC_PARAM_UD_OFFSET = 0x1A, + DEVICE_DESC_PARAM_UD_LEN = 0x1B, + DEVICE_DESC_PARAM_RTT_CAP = 0x1C, + DEVICE_DESC_PARAM_FRQ_RTC = 0x1D, +}; + /* * Logical Unit Write Protect * 00h: LU not write protected @@ -469,6 +501,7 @@ struct ufs_vreg { struct regulator *reg; const char *name; bool enabled; + bool unused; int min_uV; int max_uV; int min_uA; diff --git a/drivers/scsi/ufs/ufs_quirks.h b/drivers/scsi/ufs/ufs_quirks.h new file mode 100644 index 000000000000..ee4ab85e2801 --- /dev/null +++ b/drivers/scsi/ufs/ufs_quirks.h @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ + +#ifndef _UFS_QUIRKS_H_ +#define _UFS_QUIRKS_H_ + +/* return true if s1 is a prefix of s2 */ +#define STR_PRFX_EQUAL(s1, s2) !strncmp(s1, s2, strlen(s1)) + +#define UFS_ANY_VENDOR 0xFFFF +#define UFS_ANY_MODEL "ANY_MODEL" + +#define MAX_MODEL_LEN 16 + +#define UFS_VENDOR_TOSHIBA 0x198 +#define UFS_VENDOR_SAMSUNG 0x1CE + +/** + * ufs_device_info - ufs device details + * @wmanufacturerid: card details + * @model: card model + */ +struct ufs_device_info { + u16 wmanufacturerid; + char model[MAX_MODEL_LEN + 1]; +}; + +/** + * ufs_dev_fix - ufs device quirk info + * @card: ufs card details + * @quirk: device quirk + */ +struct ufs_dev_fix { + struct ufs_device_info card; + unsigned int quirk; +}; + +#define END_FIX { { 0 }, 0 } + +/* add specific device quirk */ +#define UFS_FIX(_vendor, _model, _quirk) \ + { \ + .card.wmanufacturerid = (_vendor),\ + .card.model = (_model), \ + .quirk = (_quirk), \ + } + +/* + * If UFS device is having issue in processing LCC (Line Control + * Command) coming from UFS host controller then enable this quirk. + * When this quirk is enabled, host controller driver should disable + * the LCC transmission on UFS host controller (by clearing + * TX_LCC_ENABLE attribute of host to 0). + */ +#define UFS_DEVICE_QUIRK_BROKEN_LCC (1 << 0) + +/* + * Some UFS devices don't need VCCQ rail for device operations. Enabling this + * quirk for such devices will make sure that VCCQ rail is not voted. + */ +#define UFS_DEVICE_NO_VCCQ (1 << 1) + +/* + * Some vendor's UFS device sends back to back NACs for the DL data frames + * causing the host controller to raise the DFES error status. Sometimes + * such UFS devices send back to back NAC without waiting for new + * retransmitted DL frame from the host and in such cases it might be possible + * the Host UniPro goes into bad state without raising the DFES error + * interrupt. If this happens then all the pending commands would timeout + * only after respective SW command (which is generally too large). + * + * We can workaround such device behaviour like this: + * - As soon as SW sees the DL NAC error, it should schedule the error handler + * - Error handler would sleep for 50ms to see if there are any fatal errors + * raised by UFS controller. + * - If there are fatal errors then SW does normal error recovery. + * - If there are no fatal errors then SW sends the NOP command to device + * to check if link is alive. + * - If NOP command times out, SW does normal error recovery + * - If NOP command succeed, skip the error handling. + * + * If DL NAC error is seen multiple times with some vendor's UFS devices then + * enable this quirk to initiate quick error recovery and also silence related + * error logs to reduce spamming of kernel logs. + */ +#define UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS (1 << 2) + +/* + * Some UFS devices may not work properly after resume if the link was kept + * in off state during suspend. Enabling this quirk will not allow the + * link to be kept in off state during suspend. + */ +#define UFS_DEVICE_QUIRK_NO_LINK_OFF (1 << 3) + +/* + * Few Toshiba UFS device models advertise RX_MIN_ACTIVATETIME_CAPABILITY as + * 600us which may not be enough for reliable hibern8 exit hardware sequence + * from UFS device. + * To workaround this issue, host should set its PA_TACTIVATE time to 1ms even + * if device advertises RX_MIN_ACTIVATETIME_CAPABILITY less than 1ms. + */ +#define UFS_DEVICE_QUIRK_PA_TACTIVATE (1 << 4) + +/* + * Some UFS memory devices may have really low read/write throughput in + * FAST AUTO mode, enable this quirk to make sure that FAST AUTO mode is + * never enabled for such devices. + */ +#define UFS_DEVICE_NO_FASTAUTO (1 << 5) + +/* + * It seems some UFS devices may keep drawing more than sleep current + * (atleast for 500us) from UFS rails (especially from VCCQ rail). + * To avoid this situation, add 2ms delay before putting these UFS + * rails in LPM mode. + */ +#define UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM (1 << 6) + +struct ufs_hba; +void ufs_advertise_fixup_device(struct ufs_hba *hba); + +static struct ufs_dev_fix ufs_fixups[] = { + /* UFS cards deviations table */ + UFS_FIX(UFS_VENDOR_SAMSUNG, UFS_ANY_MODEL, + UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM), + UFS_FIX(UFS_VENDOR_SAMSUNG, UFS_ANY_MODEL, UFS_DEVICE_NO_VCCQ), + UFS_FIX(UFS_VENDOR_SAMSUNG, UFS_ANY_MODEL, + UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS), + UFS_FIX(UFS_VENDOR_SAMSUNG, UFS_ANY_MODEL, + UFS_DEVICE_NO_FASTAUTO), + UFS_FIX(UFS_VENDOR_TOSHIBA, UFS_ANY_MODEL, + UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM), + UFS_FIX(UFS_VENDOR_TOSHIBA, "THGLF2G9C8KBADG", + UFS_DEVICE_QUIRK_PA_TACTIVATE), + UFS_FIX(UFS_VENDOR_TOSHIBA, "THGLF2G9D8KBADG", + UFS_DEVICE_QUIRK_PA_TACTIVATE), + + END_FIX +}; +#endif /* UFS_QUIRKS_H_ */ diff --git a/drivers/scsi/ufs/ufshcd-pltfrm.c b/drivers/scsi/ufs/ufshcd-pltfrm.c index d2a7b127b05c..718f12e09885 100644 --- a/drivers/scsi/ufs/ufshcd-pltfrm.c +++ b/drivers/scsi/ufs/ufshcd-pltfrm.c @@ -40,6 +40,8 @@ #include "ufshcd.h" #include "ufshcd-pltfrm.h" +#define UFSHCD_DEFAULT_LANES_PER_DIRECTION 2 + static int ufshcd_parse_clock_info(struct ufs_hba *hba) { int ret = 0; @@ -277,6 +279,21 @@ void ufshcd_pltfrm_shutdown(struct platform_device *pdev) } EXPORT_SYMBOL_GPL(ufshcd_pltfrm_shutdown); +static void ufshcd_init_lanes_per_dir(struct ufs_hba *hba) +{ + struct device *dev = hba->dev; + int ret; + + ret = of_property_read_u32(dev->of_node, "lanes-per-direction", + &hba->lanes_per_direction); + if (ret) { + dev_dbg(hba->dev, + "%s: failed to read lanes-per-direction, ret=%d\n", + __func__, ret); + hba->lanes_per_direction = UFSHCD_DEFAULT_LANES_PER_DIRECTION; + } +} + /** * ufshcd_pltfrm_init - probe routine of the driver * @pdev: pointer to Platform device handle @@ -331,6 +348,8 @@ int ufshcd_pltfrm_init(struct platform_device *pdev, pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); + ufshcd_init_lanes_per_dir(hba); + err = ufshcd_init(hba, mmio_base, irq); if (err) { dev_err(dev, "Initialization failed\n"); diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 9c1b94bef8f3..f8fa72c31a9d 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -39,8 +39,10 @@ #include <linux/async.h> #include <linux/devfreq.h> - +#include <linux/nls.h> +#include <linux/of.h> #include "ufshcd.h" +#include "ufs_quirks.h" #include "unipro.h" #define UFSHCD_ENABLE_INTRS (UTP_TRANSFER_REQ_COMPL |\ @@ -131,9 +133,11 @@ enum { /* UFSHCD UIC layer error flags */ enum { UFSHCD_UIC_DL_PA_INIT_ERROR = (1 << 0), /* Data link layer error */ - UFSHCD_UIC_NL_ERROR = (1 << 1), /* Network layer error */ - UFSHCD_UIC_TL_ERROR = (1 << 2), /* Transport Layer error */ - UFSHCD_UIC_DME_ERROR = (1 << 3), /* DME error */ + UFSHCD_UIC_DL_NAC_RECEIVED_ERROR = (1 << 1), /* Data link layer error */ + UFSHCD_UIC_DL_TCx_REPLAY_ERROR = (1 << 2), /* Data link layer error */ + UFSHCD_UIC_NL_ERROR = (1 << 3), /* Network layer error */ + UFSHCD_UIC_TL_ERROR = (1 << 4), /* Transport Layer error */ + UFSHCD_UIC_DME_ERROR = (1 << 5), /* DME error */ }; /* Interrupt configuration options */ @@ -193,6 +197,7 @@ static int ufshcd_probe_hba(struct ufs_hba *hba); static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on, bool skip_ref_clk); static int ufshcd_setup_clocks(struct ufs_hba *hba, bool on); +static int ufshcd_set_vccq_rail_unused(struct ufs_hba *hba, bool unused); static int ufshcd_uic_hibern8_exit(struct ufs_hba *hba); static int ufshcd_uic_hibern8_enter(struct ufs_hba *hba); static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba); @@ -231,6 +236,16 @@ static inline void ufshcd_disable_irq(struct ufs_hba *hba) } } +/* replace non-printable or non-ASCII characters with spaces */ +static inline void ufshcd_remove_non_printable(char *val) +{ + if (!val) + return; + + if (*val < 0x20 || *val > 0x7e) + *val = ' '; +} + /* * ufshcd_wait_for_register - wait for register value to change * @hba - per-adapter interface @@ -239,11 +254,13 @@ static inline void ufshcd_disable_irq(struct ufs_hba *hba) * @val - wait condition * @interval_us - polling interval in microsecs * @timeout_ms - timeout in millisecs + * @can_sleep - perform sleep or just spin * * Returns -ETIMEDOUT on error, zero on success */ -static int ufshcd_wait_for_register(struct ufs_hba *hba, u32 reg, u32 mask, - u32 val, unsigned long interval_us, unsigned long timeout_ms) +int ufshcd_wait_for_register(struct ufs_hba *hba, u32 reg, u32 mask, + u32 val, unsigned long interval_us, + unsigned long timeout_ms, bool can_sleep) { int err = 0; unsigned long timeout = jiffies + msecs_to_jiffies(timeout_ms); @@ -252,9 +269,10 @@ static int ufshcd_wait_for_register(struct ufs_hba *hba, u32 reg, u32 mask, val = val & mask; while ((ufshcd_readl(hba, reg) & mask) != val) { - /* wakeup within 50us of expiry */ - usleep_range(interval_us, interval_us + 50); - + if (can_sleep) + usleep_range(interval_us, interval_us + 50); + else + udelay(interval_us); if (time_after(jiffies, timeout)) { if ((ufshcd_readl(hba, reg) & mask) != val) err = -ETIMEDOUT; @@ -552,6 +570,34 @@ static inline int ufshcd_is_hba_active(struct ufs_hba *hba) return (ufshcd_readl(hba, REG_CONTROLLER_ENABLE) & 0x1) ? 0 : 1; } +u32 ufshcd_get_local_unipro_ver(struct ufs_hba *hba) +{ + /* HCI version 1.0 and 1.1 supports UniPro 1.41 */ + if ((hba->ufs_version == UFSHCI_VERSION_10) || + (hba->ufs_version == UFSHCI_VERSION_11)) + return UFS_UNIPRO_VER_1_41; + else + return UFS_UNIPRO_VER_1_6; +} +EXPORT_SYMBOL(ufshcd_get_local_unipro_ver); + +static bool ufshcd_is_unipro_pa_params_tuning_req(struct ufs_hba *hba) +{ + /* + * If both host and device support UniPro ver1.6 or later, PA layer + * parameters tuning happens during link startup itself. + * + * We can manually tune PA layer parameters if either host or device + * doesn't support UniPro ver 1.6 or later. But to keep manual tuning + * logic simple, we will only do manual tuning if local unipro version + * doesn't support ver1.6 or later. + */ + if (ufshcd_get_local_unipro_ver(hba) < UFS_UNIPRO_VER_1_6) + return true; + else + return false; +} + static void ufshcd_ungate_work(struct work_struct *work) { int ret; @@ -1458,7 +1504,7 @@ ufshcd_clear_cmd(struct ufs_hba *hba, int tag) */ err = ufshcd_wait_for_register(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL, - mask, ~mask, 1000, 1000); + mask, ~mask, 1000, 1000, true); return err; } @@ -1857,21 +1903,7 @@ static int ufshcd_query_attr_retry(struct ufs_hba *hba, return ret; } -/** - * ufshcd_query_descriptor - API function for sending descriptor requests - * hba: per-adapter instance - * opcode: attribute opcode - * idn: attribute idn to access - * index: index field - * selector: selector field - * desc_buf: the buffer that contains the descriptor - * buf_len: length parameter passed to the device - * - * Returns 0 for success, non-zero in case of failure. - * The buf_len parameter will contain, on return, the length parameter - * received on the response. - */ -static int ufshcd_query_descriptor(struct ufs_hba *hba, +static int __ufshcd_query_descriptor(struct ufs_hba *hba, enum query_opcode opcode, enum desc_idn idn, u8 index, u8 selector, u8 *desc_buf, int *buf_len) { @@ -1936,6 +1968,39 @@ out: } /** + * ufshcd_query_descriptor_retry - API function for sending descriptor + * requests + * hba: per-adapter instance + * opcode: attribute opcode + * idn: attribute idn to access + * index: index field + * selector: selector field + * desc_buf: the buffer that contains the descriptor + * buf_len: length parameter passed to the device + * + * Returns 0 for success, non-zero in case of failure. + * The buf_len parameter will contain, on return, the length parameter + * received on the response. + */ +int ufshcd_query_descriptor_retry(struct ufs_hba *hba, + enum query_opcode opcode, enum desc_idn idn, u8 index, + u8 selector, u8 *desc_buf, int *buf_len) +{ + int err; + int retries; + + for (retries = QUERY_REQ_RETRIES; retries > 0; retries--) { + err = __ufshcd_query_descriptor(hba, opcode, idn, index, + selector, desc_buf, buf_len); + if (!err || err == -EINVAL) + break; + } + + return err; +} +EXPORT_SYMBOL(ufshcd_query_descriptor_retry); + +/** * ufshcd_read_desc_param - read the specified descriptor parameter * @hba: Pointer to adapter instance * @desc_id: descriptor idn value @@ -1977,9 +2042,9 @@ static int ufshcd_read_desc_param(struct ufs_hba *hba, return -ENOMEM; } - ret = ufshcd_query_descriptor(hba, UPIU_QUERY_OPCODE_READ_DESC, - desc_id, desc_index, 0, desc_buf, - &buff_len); + ret = ufshcd_query_descriptor_retry(hba, UPIU_QUERY_OPCODE_READ_DESC, + desc_id, desc_index, 0, desc_buf, + &buff_len); if (ret || (buff_len < ufs_query_desc_max_size[desc_id]) || (desc_buf[QUERY_DESC_LENGTH_OFFSET] != @@ -2017,6 +2082,82 @@ static inline int ufshcd_read_power_desc(struct ufs_hba *hba, return ufshcd_read_desc(hba, QUERY_DESC_IDN_POWER, 0, buf, size); } +int ufshcd_read_device_desc(struct ufs_hba *hba, u8 *buf, u32 size) +{ + return ufshcd_read_desc(hba, QUERY_DESC_IDN_DEVICE, 0, buf, size); +} +EXPORT_SYMBOL(ufshcd_read_device_desc); + +/** + * ufshcd_read_string_desc - read string descriptor + * @hba: pointer to adapter instance + * @desc_index: descriptor index + * @buf: pointer to buffer where descriptor would be read + * @size: size of buf + * @ascii: if true convert from unicode to ascii characters + * + * Return 0 in case of success, non-zero otherwise + */ +int ufshcd_read_string_desc(struct ufs_hba *hba, int desc_index, u8 *buf, + u32 size, bool ascii) +{ + int err = 0; + + err = ufshcd_read_desc(hba, + QUERY_DESC_IDN_STRING, desc_index, buf, size); + + if (err) { + dev_err(hba->dev, "%s: reading String Desc failed after %d retries. err = %d\n", + __func__, QUERY_REQ_RETRIES, err); + goto out; + } + + if (ascii) { + int desc_len; + int ascii_len; + int i; + char *buff_ascii; + + desc_len = buf[0]; + /* remove header and divide by 2 to move from UTF16 to UTF8 */ + ascii_len = (desc_len - QUERY_DESC_HDR_SIZE) / 2 + 1; + if (size < ascii_len + QUERY_DESC_HDR_SIZE) { + dev_err(hba->dev, "%s: buffer allocated size is too small\n", + __func__); + err = -ENOMEM; + goto out; + } + + buff_ascii = kmalloc(ascii_len, GFP_KERNEL); + if (!buff_ascii) { + err = -ENOMEM; + goto out_free_buff; + } + + /* + * the descriptor contains string in UTF16 format + * we need to convert to utf-8 so it can be displayed + */ + utf16s_to_utf8s((wchar_t *)&buf[QUERY_DESC_HDR_SIZE], + desc_len - QUERY_DESC_HDR_SIZE, + UTF16_BIG_ENDIAN, buff_ascii, ascii_len); + + /* replace non-printable or non-ASCII characters with spaces */ + for (i = 0; i < ascii_len; i++) + ufshcd_remove_non_printable(&buff_ascii[i]); + + memset(buf + QUERY_DESC_HDR_SIZE, 0, + size - QUERY_DESC_HDR_SIZE); + memcpy(buf + QUERY_DESC_HDR_SIZE, buff_ascii, ascii_len); + buf[QUERY_DESC_LENGTH_OFFSET] = ascii_len + QUERY_DESC_HDR_SIZE; +out_free_buff: + kfree(buff_ascii); + } +out: + return err; +} +EXPORT_SYMBOL(ufshcd_read_string_desc); + /** * ufshcd_read_unit_desc_param - read the specified unit descriptor parameter * @hba: Pointer to adapter instance @@ -2814,6 +2955,23 @@ out: } /** + * ufshcd_hba_stop - Send controller to reset state + * @hba: per adapter instance + * @can_sleep: perform sleep or just spin + */ +static inline void ufshcd_hba_stop(struct ufs_hba *hba, bool can_sleep) +{ + int err; + + ufshcd_writel(hba, CONTROLLER_DISABLE, REG_CONTROLLER_ENABLE); + err = ufshcd_wait_for_register(hba, REG_CONTROLLER_ENABLE, + CONTROLLER_ENABLE, CONTROLLER_DISABLE, + 10, 1, can_sleep); + if (err) + dev_err(hba->dev, "%s: Controller disable failed\n", __func__); +} + +/** * ufshcd_hba_enable - initialize the controller * @hba: per adapter instance * @@ -2833,18 +2991,9 @@ static int ufshcd_hba_enable(struct ufs_hba *hba) * development and testing of this driver. msleep can be changed to * mdelay and retry count can be reduced based on the controller. */ - if (!ufshcd_is_hba_active(hba)) { - + if (!ufshcd_is_hba_active(hba)) /* change controller state to "reset state" */ - ufshcd_hba_stop(hba); - - /* - * This delay is based on the testing done with UFS host - * controller FPGA. The delay can be changed based on the - * host controller used. - */ - msleep(5); - } + ufshcd_hba_stop(hba, true); /* UniPro link is disabled at this point */ ufshcd_set_link_off(hba); @@ -3365,31 +3514,18 @@ static void ufshcd_uic_cmd_compl(struct ufs_hba *hba, u32 intr_status) } /** - * ufshcd_transfer_req_compl - handle SCSI and query command completion + * __ufshcd_transfer_req_compl - handle SCSI and query command completion * @hba: per adapter instance + * @completed_reqs: requests to complete */ -static void ufshcd_transfer_req_compl(struct ufs_hba *hba) +static void __ufshcd_transfer_req_compl(struct ufs_hba *hba, + unsigned long completed_reqs) { struct ufshcd_lrb *lrbp; struct scsi_cmnd *cmd; - unsigned long completed_reqs; - u32 tr_doorbell; int result; int index; - /* Resetting interrupt aggregation counters first and reading the - * DOOR_BELL afterward allows us to handle all the completed requests. - * In order to prevent other interrupts starvation the DB is read once - * after reset. The down side of this solution is the possibility of - * false interrupt if device completes another request after resetting - * aggregation and before reading the DB. - */ - if (ufshcd_is_intr_aggr_allowed(hba)) - ufshcd_reset_intr_aggr(hba); - - tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL); - completed_reqs = tr_doorbell ^ hba->outstanding_reqs; - for_each_set_bit(index, &completed_reqs, hba->nutrs) { lrbp = &hba->lrb[index]; cmd = lrbp->cmd; @@ -3419,6 +3555,31 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba) } /** + * ufshcd_transfer_req_compl - handle SCSI and query command completion + * @hba: per adapter instance + */ +static void ufshcd_transfer_req_compl(struct ufs_hba *hba) +{ + unsigned long completed_reqs; + u32 tr_doorbell; + + /* Resetting interrupt aggregation counters first and reading the + * DOOR_BELL afterward allows us to handle all the completed requests. + * In order to prevent other interrupts starvation the DB is read once + * after reset. The down side of this solution is the possibility of + * false interrupt if device completes another request after resetting + * aggregation and before reading the DB. + */ + if (ufshcd_is_intr_aggr_allowed(hba)) + ufshcd_reset_intr_aggr(hba); + + tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL); + completed_reqs = tr_doorbell ^ hba->outstanding_reqs; + + __ufshcd_transfer_req_compl(hba, completed_reqs); +} + +/** * ufshcd_disable_ee - disable exception event * @hba: per-adapter instance * @mask: exception event to disable @@ -3630,7 +3791,7 @@ out: */ static int ufshcd_urgent_bkops(struct ufs_hba *hba) { - return ufshcd_bkops_ctrl(hba, BKOPS_STATUS_PERF_IMPACT); + return ufshcd_bkops_ctrl(hba, hba->urgent_bkops_lvl); } static inline int ufshcd_get_ee_status(struct ufs_hba *hba, u32 *status) @@ -3639,6 +3800,43 @@ static inline int ufshcd_get_ee_status(struct ufs_hba *hba, u32 *status) QUERY_ATTR_IDN_EE_STATUS, 0, 0, status); } +static void ufshcd_bkops_exception_event_handler(struct ufs_hba *hba) +{ + int err; + u32 curr_status = 0; + + if (hba->is_urgent_bkops_lvl_checked) + goto enable_auto_bkops; + + err = ufshcd_get_bkops_status(hba, &curr_status); + if (err) { + dev_err(hba->dev, "%s: failed to get BKOPS status %d\n", + __func__, err); + goto out; + } + + /* + * We are seeing that some devices are raising the urgent bkops + * exception events even when BKOPS status doesn't indicate performace + * impacted or critical. Handle these device by determining their urgent + * bkops status at runtime. + */ + if (curr_status < BKOPS_STATUS_PERF_IMPACT) { + dev_err(hba->dev, "%s: device raised urgent BKOPS exception for bkops status %d\n", + __func__, curr_status); + /* update the current status as the urgent bkops level */ + hba->urgent_bkops_lvl = curr_status; + hba->is_urgent_bkops_lvl_checked = true; + } + +enable_auto_bkops: + err = ufshcd_enable_auto_bkops(hba); +out: + if (err < 0) + dev_err(hba->dev, "%s: failed to handle urgent bkops %d\n", + __func__, err); +} + /** * ufshcd_exception_event_handler - handle exceptions raised by device * @work: pointer to work data @@ -3662,17 +3860,95 @@ static void ufshcd_exception_event_handler(struct work_struct *work) } status &= hba->ee_ctrl_mask; - if (status & MASK_EE_URGENT_BKOPS) { - err = ufshcd_urgent_bkops(hba); - if (err < 0) - dev_err(hba->dev, "%s: failed to handle urgent bkops %d\n", - __func__, err); - } + + if (status & MASK_EE_URGENT_BKOPS) + ufshcd_bkops_exception_event_handler(hba); + out: pm_runtime_put_sync(hba->dev); return; } +/* Complete requests that have door-bell cleared */ +static void ufshcd_complete_requests(struct ufs_hba *hba) +{ + ufshcd_transfer_req_compl(hba); + ufshcd_tmc_handler(hba); +} + +/** + * ufshcd_quirk_dl_nac_errors - This function checks if error handling is + * to recover from the DL NAC errors or not. + * @hba: per-adapter instance + * + * Returns true if error handling is required, false otherwise + */ +static bool ufshcd_quirk_dl_nac_errors(struct ufs_hba *hba) +{ + unsigned long flags; + bool err_handling = true; + + spin_lock_irqsave(hba->host->host_lock, flags); + /* + * UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS only workaround the + * device fatal error and/or DL NAC & REPLAY timeout errors. + */ + if (hba->saved_err & (CONTROLLER_FATAL_ERROR | SYSTEM_BUS_FATAL_ERROR)) + goto out; + + if ((hba->saved_err & DEVICE_FATAL_ERROR) || + ((hba->saved_err & UIC_ERROR) && + (hba->saved_uic_err & UFSHCD_UIC_DL_TCx_REPLAY_ERROR))) + goto out; + + if ((hba->saved_err & UIC_ERROR) && + (hba->saved_uic_err & UFSHCD_UIC_DL_NAC_RECEIVED_ERROR)) { + int err; + /* + * wait for 50ms to see if we can get any other errors or not. + */ + spin_unlock_irqrestore(hba->host->host_lock, flags); + msleep(50); + spin_lock_irqsave(hba->host->host_lock, flags); + + /* + * now check if we have got any other severe errors other than + * DL NAC error? + */ + if ((hba->saved_err & INT_FATAL_ERRORS) || + ((hba->saved_err & UIC_ERROR) && + (hba->saved_uic_err & ~UFSHCD_UIC_DL_NAC_RECEIVED_ERROR))) + goto out; + + /* + * As DL NAC is the only error received so far, send out NOP + * command to confirm if link is still active or not. + * - If we don't get any response then do error recovery. + * - If we get response then clear the DL NAC error bit. + */ + + spin_unlock_irqrestore(hba->host->host_lock, flags); + err = ufshcd_verify_dev_init(hba); + spin_lock_irqsave(hba->host->host_lock, flags); + + if (err) + goto out; + + /* Link seems to be alive hence ignore the DL NAC errors */ + if (hba->saved_uic_err == UFSHCD_UIC_DL_NAC_RECEIVED_ERROR) + hba->saved_err &= ~UIC_ERROR; + /* clear NAC error */ + hba->saved_uic_err &= ~UFSHCD_UIC_DL_NAC_RECEIVED_ERROR; + if (!hba->saved_uic_err) { + err_handling = false; + goto out; + } + } +out: + spin_unlock_irqrestore(hba->host->host_lock, flags); + return err_handling; +} + /** * ufshcd_err_handler - handle UFS errors that require s/w attention * @work: pointer to work structure @@ -3685,6 +3961,7 @@ static void ufshcd_err_handler(struct work_struct *work) u32 err_tm = 0; int err = 0; int tag; + bool needs_reset = false; hba = container_of(work, struct ufs_hba, eh_work); @@ -3692,40 +3969,86 @@ static void ufshcd_err_handler(struct work_struct *work) ufshcd_hold(hba, false); spin_lock_irqsave(hba->host->host_lock, flags); - if (hba->ufshcd_state == UFSHCD_STATE_RESET) { - spin_unlock_irqrestore(hba->host->host_lock, flags); + if (hba->ufshcd_state == UFSHCD_STATE_RESET) goto out; - } hba->ufshcd_state = UFSHCD_STATE_RESET; ufshcd_set_eh_in_progress(hba); /* Complete requests that have door-bell cleared by h/w */ - ufshcd_transfer_req_compl(hba); - ufshcd_tmc_handler(hba); - spin_unlock_irqrestore(hba->host->host_lock, flags); + ufshcd_complete_requests(hba); + + if (hba->dev_quirks & UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS) { + bool ret; + spin_unlock_irqrestore(hba->host->host_lock, flags); + /* release the lock as ufshcd_quirk_dl_nac_errors() may sleep */ + ret = ufshcd_quirk_dl_nac_errors(hba); + spin_lock_irqsave(hba->host->host_lock, flags); + if (!ret) + goto skip_err_handling; + } + if ((hba->saved_err & INT_FATAL_ERRORS) || + ((hba->saved_err & UIC_ERROR) && + (hba->saved_uic_err & (UFSHCD_UIC_DL_PA_INIT_ERROR | + UFSHCD_UIC_DL_NAC_RECEIVED_ERROR | + UFSHCD_UIC_DL_TCx_REPLAY_ERROR)))) + needs_reset = true; + + /* + * if host reset is required then skip clearing the pending + * transfers forcefully because they will automatically get + * cleared after link startup. + */ + if (needs_reset) + goto skip_pending_xfer_clear; + + /* release lock as clear command might sleep */ + spin_unlock_irqrestore(hba->host->host_lock, flags); /* Clear pending transfer requests */ - for_each_set_bit(tag, &hba->outstanding_reqs, hba->nutrs) - if (ufshcd_clear_cmd(hba, tag)) - err_xfer |= 1 << tag; + for_each_set_bit(tag, &hba->outstanding_reqs, hba->nutrs) { + if (ufshcd_clear_cmd(hba, tag)) { + err_xfer = true; + goto lock_skip_pending_xfer_clear; + } + } /* Clear pending task management requests */ - for_each_set_bit(tag, &hba->outstanding_tasks, hba->nutmrs) - if (ufshcd_clear_tm_cmd(hba, tag)) - err_tm |= 1 << tag; + for_each_set_bit(tag, &hba->outstanding_tasks, hba->nutmrs) { + if (ufshcd_clear_tm_cmd(hba, tag)) { + err_tm = true; + goto lock_skip_pending_xfer_clear; + } + } - /* Complete the requests that are cleared by s/w */ +lock_skip_pending_xfer_clear: spin_lock_irqsave(hba->host->host_lock, flags); - ufshcd_transfer_req_compl(hba); - ufshcd_tmc_handler(hba); - spin_unlock_irqrestore(hba->host->host_lock, flags); + /* Complete the requests that are cleared by s/w */ + ufshcd_complete_requests(hba); + + if (err_xfer || err_tm) + needs_reset = true; + +skip_pending_xfer_clear: /* Fatal errors need reset */ - if (err_xfer || err_tm || (hba->saved_err & INT_FATAL_ERRORS) || - ((hba->saved_err & UIC_ERROR) && - (hba->saved_uic_err & UFSHCD_UIC_DL_PA_INIT_ERROR))) { + if (needs_reset) { + unsigned long max_doorbells = (1UL << hba->nutrs) - 1; + + /* + * ufshcd_reset_and_restore() does the link reinitialization + * which will need atleast one empty doorbell slot to send the + * device management commands (NOP and query commands). + * If there is no slot empty at this moment then free up last + * slot forcefully. + */ + if (hba->outstanding_reqs == max_doorbells) + __ufshcd_transfer_req_compl(hba, + (1UL << (hba->nutrs - 1))); + + spin_unlock_irqrestore(hba->host->host_lock, flags); err = ufshcd_reset_and_restore(hba); + spin_lock_irqsave(hba->host->host_lock, flags); if (err) { dev_err(hba->dev, "%s: reset and restore failed\n", __func__); @@ -3739,9 +4062,19 @@ static void ufshcd_err_handler(struct work_struct *work) hba->saved_err = 0; hba->saved_uic_err = 0; } + +skip_err_handling: + if (!needs_reset) { + hba->ufshcd_state = UFSHCD_STATE_OPERATIONAL; + if (hba->saved_err || hba->saved_uic_err) + dev_err_ratelimited(hba->dev, "%s: exit: saved_err 0x%x saved_uic_err 0x%x", + __func__, hba->saved_err, hba->saved_uic_err); + } + ufshcd_clear_eh_in_progress(hba); out: + spin_unlock_irqrestore(hba->host->host_lock, flags); scsi_unblock_requests(hba->host); ufshcd_release(hba); pm_runtime_put_sync(hba->dev); @@ -3759,6 +4092,14 @@ static void ufshcd_update_uic_error(struct ufs_hba *hba) reg = ufshcd_readl(hba, REG_UIC_ERROR_CODE_DATA_LINK_LAYER); if (reg & UIC_DATA_LINK_LAYER_ERROR_PA_INIT) hba->uic_error |= UFSHCD_UIC_DL_PA_INIT_ERROR; + else if (hba->dev_quirks & + UFS_DEVICE_QUIRK_RECOVERY_FROM_DL_NAC_ERRORS) { + if (reg & UIC_DATA_LINK_LAYER_ERROR_NAC_RECEIVED) + hba->uic_error |= + UFSHCD_UIC_DL_NAC_RECEIVED_ERROR; + else if (reg & UIC_DATA_LINK_LAYER_ERROR_TCx_REPLAY_TIMEOUT) + hba->uic_error |= UFSHCD_UIC_DL_TCx_REPLAY_ERROR; + } /* UIC NL/TL/DME errors needs software retry */ reg = ufshcd_readl(hba, REG_UIC_ERROR_CODE_NETWORK_LAYER); @@ -3796,15 +4137,18 @@ static void ufshcd_check_errors(struct ufs_hba *hba) } if (queue_eh_work) { + /* + * update the transfer error masks to sticky bits, let's do this + * irrespective of current ufshcd_state. + */ + hba->saved_err |= hba->errors; + hba->saved_uic_err |= hba->uic_error; + /* handle fatal errors only when link is functional */ if (hba->ufshcd_state == UFSHCD_STATE_OPERATIONAL) { /* block commands from scsi mid-layer */ scsi_block_requests(hba->host); - /* transfer error masks to sticky bits */ - hba->saved_err |= hba->errors; - hba->saved_uic_err |= hba->uic_error; - hba->ufshcd_state = UFSHCD_STATE_ERROR; schedule_work(&hba->eh_work); } @@ -3897,7 +4241,7 @@ static int ufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag) /* poll for max. 1 sec to clear door bell register by h/w */ err = ufshcd_wait_for_register(hba, REG_UTP_TASK_REQ_DOOR_BELL, - mask, 0, 1000, 1000); + mask, 0, 1000, 1000, true); out: return err; } @@ -4179,7 +4523,7 @@ static int ufshcd_host_reset_and_restore(struct ufs_hba *hba) /* Reset the host controller */ spin_lock_irqsave(hba->host->host_lock, flags); - ufshcd_hba_stop(hba); + ufshcd_hba_stop(hba, false); spin_unlock_irqrestore(hba->host->host_lock, flags); err = ufshcd_hba_enable(hba); @@ -4466,6 +4810,164 @@ out: return ret; } +static int ufs_get_device_info(struct ufs_hba *hba, + struct ufs_device_info *card_data) +{ + int err; + u8 model_index; + u8 str_desc_buf[QUERY_DESC_STRING_MAX_SIZE + 1] = {0}; + u8 desc_buf[QUERY_DESC_DEVICE_MAX_SIZE]; + + err = ufshcd_read_device_desc(hba, desc_buf, + QUERY_DESC_DEVICE_MAX_SIZE); + if (err) { + dev_err(hba->dev, "%s: Failed reading Device Desc. err = %d\n", + __func__, err); + goto out; + } + + /* + * getting vendor (manufacturerID) and Bank Index in big endian + * format + */ + card_data->wmanufacturerid = desc_buf[DEVICE_DESC_PARAM_MANF_ID] << 8 | + desc_buf[DEVICE_DESC_PARAM_MANF_ID + 1]; + + model_index = desc_buf[DEVICE_DESC_PARAM_PRDCT_NAME]; + + err = ufshcd_read_string_desc(hba, model_index, str_desc_buf, + QUERY_DESC_STRING_MAX_SIZE, ASCII_STD); + if (err) { + dev_err(hba->dev, "%s: Failed reading Product Name. err = %d\n", + __func__, err); + goto out; + } + + str_desc_buf[QUERY_DESC_STRING_MAX_SIZE] = '\0'; + strlcpy(card_data->model, (str_desc_buf + QUERY_DESC_HDR_SIZE), + min_t(u8, str_desc_buf[QUERY_DESC_LENGTH_OFFSET], + MAX_MODEL_LEN)); + + /* Null terminate the model string */ + card_data->model[MAX_MODEL_LEN] = '\0'; + +out: + return err; +} + +void ufs_advertise_fixup_device(struct ufs_hba *hba) +{ + int err; + struct ufs_dev_fix *f; + struct ufs_device_info card_data; + + card_data.wmanufacturerid = 0; + + err = ufs_get_device_info(hba, &card_data); + if (err) { + dev_err(hba->dev, "%s: Failed getting device info. err = %d\n", + __func__, err); + return; + } + + for (f = ufs_fixups; f->quirk; f++) { + if (((f->card.wmanufacturerid == card_data.wmanufacturerid) || + (f->card.wmanufacturerid == UFS_ANY_VENDOR)) && + (STR_PRFX_EQUAL(f->card.model, card_data.model) || + !strcmp(f->card.model, UFS_ANY_MODEL))) + hba->dev_quirks |= f->quirk; + } +} + +/** + * ufshcd_tune_pa_tactivate - Tunes PA_TActivate of local UniPro + * @hba: per-adapter instance + * + * PA_TActivate parameter can be tuned manually if UniPro version is less than + * 1.61. PA_TActivate needs to be greater than or equal to peerM-PHY's + * RX_MIN_ACTIVATETIME_CAPABILITY attribute. This optimal value can help reduce + * the hibern8 exit latency. + * + * Returns zero on success, non-zero error value on failure. + */ +static int ufshcd_tune_pa_tactivate(struct ufs_hba *hba) +{ + int ret = 0; + u32 peer_rx_min_activatetime = 0, tuned_pa_tactivate; + + ret = ufshcd_dme_peer_get(hba, + UIC_ARG_MIB_SEL( + RX_MIN_ACTIVATETIME_CAPABILITY, + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), + &peer_rx_min_activatetime); + if (ret) + goto out; + + /* make sure proper unit conversion is applied */ + tuned_pa_tactivate = + ((peer_rx_min_activatetime * RX_MIN_ACTIVATETIME_UNIT_US) + / PA_TACTIVATE_TIME_UNIT_US); + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TACTIVATE), + tuned_pa_tactivate); + +out: + return ret; +} + +/** + * ufshcd_tune_pa_hibern8time - Tunes PA_Hibern8Time of local UniPro + * @hba: per-adapter instance + * + * PA_Hibern8Time parameter can be tuned manually if UniPro version is less than + * 1.61. PA_Hibern8Time needs to be maximum of local M-PHY's + * TX_HIBERN8TIME_CAPABILITY & peer M-PHY's RX_HIBERN8TIME_CAPABILITY. + * This optimal value can help reduce the hibern8 exit latency. + * + * Returns zero on success, non-zero error value on failure. + */ +static int ufshcd_tune_pa_hibern8time(struct ufs_hba *hba) +{ + int ret = 0; + u32 local_tx_hibern8_time_cap = 0, peer_rx_hibern8_time_cap = 0; + u32 max_hibern8_time, tuned_pa_hibern8time; + + ret = ufshcd_dme_get(hba, + UIC_ARG_MIB_SEL(TX_HIBERN8TIME_CAPABILITY, + UIC_ARG_MPHY_TX_GEN_SEL_INDEX(0)), + &local_tx_hibern8_time_cap); + if (ret) + goto out; + + ret = ufshcd_dme_peer_get(hba, + UIC_ARG_MIB_SEL(RX_HIBERN8TIME_CAPABILITY, + UIC_ARG_MPHY_RX_GEN_SEL_INDEX(0)), + &peer_rx_hibern8_time_cap); + if (ret) + goto out; + + max_hibern8_time = max(local_tx_hibern8_time_cap, + peer_rx_hibern8_time_cap); + /* make sure proper unit conversion is applied */ + tuned_pa_hibern8time = ((max_hibern8_time * HIBERN8TIME_UNIT_US) + / PA_HIBERN8_TIME_UNIT_US); + ret = ufshcd_dme_set(hba, UIC_ARG_MIB(PA_HIBERN8TIME), + tuned_pa_hibern8time); +out: + return ret; +} + +static void ufshcd_tune_unipro_params(struct ufs_hba *hba) +{ + if (ufshcd_is_unipro_pa_params_tuning_req(hba)) { + ufshcd_tune_pa_tactivate(hba); + ufshcd_tune_pa_hibern8time(hba); + } + + if (hba->dev_quirks & UFS_DEVICE_QUIRK_PA_TACTIVATE) + /* set 1ms timeout for PA_TACTIVATE */ + ufshcd_dme_set(hba, UIC_ARG_MIB(PA_TACTIVATE), 10); +} + /** * ufshcd_probe_hba - probe hba to detect device and initialize * @hba: per-adapter instance @@ -4482,6 +4984,10 @@ static int ufshcd_probe_hba(struct ufs_hba *hba) ufshcd_init_pwr_info(hba); + /* set the default level for urgent bkops */ + hba->urgent_bkops_lvl = BKOPS_STATUS_PERF_IMPACT; + hba->is_urgent_bkops_lvl_checked = false; + /* UniPro link is active now */ ufshcd_set_link_active(hba); @@ -4493,6 +4999,14 @@ static int ufshcd_probe_hba(struct ufs_hba *hba) if (ret) goto out; + ufs_advertise_fixup_device(hba); + ufshcd_tune_unipro_params(hba); + + ret = ufshcd_set_vccq_rail_unused(hba, + (hba->dev_quirks & UFS_DEVICE_NO_VCCQ) ? true : false); + if (ret) + goto out; + /* UFS device is also active now */ ufshcd_set_ufs_dev_active(hba); ufshcd_force_reset_auto_bkops(hba); @@ -4567,6 +5081,41 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) ufshcd_probe_hba(hba); } +static enum blk_eh_timer_return ufshcd_eh_timed_out(struct scsi_cmnd *scmd) +{ + unsigned long flags; + struct Scsi_Host *host; + struct ufs_hba *hba; + int index; + bool found = false; + + if (!scmd || !scmd->device || !scmd->device->host) + return BLK_EH_NOT_HANDLED; + + host = scmd->device->host; + hba = shost_priv(host); + if (!hba) + return BLK_EH_NOT_HANDLED; + + spin_lock_irqsave(host->host_lock, flags); + + for_each_set_bit(index, &hba->outstanding_reqs, hba->nutrs) { + if (hba->lrb[index].cmd == scmd) { + found = true; + break; + } + } + + spin_unlock_irqrestore(host->host_lock, flags); + + /* + * Bypass SCSI error handling and reset the block layer timer if this + * SCSI command was not actually dispatched to UFS driver, otherwise + * let SCSI layer handle the error as usual. + */ + return found ? BLK_EH_NOT_HANDLED : BLK_EH_RESET_TIMER; +} + static struct scsi_host_template ufshcd_driver_template = { .module = THIS_MODULE, .name = UFSHCD, @@ -4579,6 +5128,7 @@ static struct scsi_host_template ufshcd_driver_template = { .eh_abort_handler = ufshcd_abort, .eh_device_reset_handler = ufshcd_eh_device_reset_handler, .eh_host_reset_handler = ufshcd_eh_host_reset_handler, + .eh_timed_out = ufshcd_eh_timed_out, .this_id = -1, .sg_tablesize = SG_ALL, .cmd_per_lun = UFSHCD_CMD_PER_LUN, @@ -4607,13 +5157,24 @@ static int ufshcd_config_vreg_load(struct device *dev, struct ufs_vreg *vreg, static inline int ufshcd_config_vreg_lpm(struct ufs_hba *hba, struct ufs_vreg *vreg) { - return ufshcd_config_vreg_load(hba->dev, vreg, UFS_VREG_LPM_LOAD_UA); + if (!vreg) + return 0; + else if (vreg->unused) + return 0; + else + return ufshcd_config_vreg_load(hba->dev, vreg, + UFS_VREG_LPM_LOAD_UA); } static inline int ufshcd_config_vreg_hpm(struct ufs_hba *hba, struct ufs_vreg *vreg) { - return ufshcd_config_vreg_load(hba->dev, vreg, vreg->max_uA); + if (!vreg) + return 0; + else if (vreg->unused) + return 0; + else + return ufshcd_config_vreg_load(hba->dev, vreg, vreg->max_uA); } static int ufshcd_config_vreg(struct device *dev, @@ -4648,7 +5209,9 @@ static int ufshcd_enable_vreg(struct device *dev, struct ufs_vreg *vreg) { int ret = 0; - if (!vreg || vreg->enabled) + if (!vreg) + goto out; + else if (vreg->enabled || vreg->unused) goto out; ret = ufshcd_config_vreg(dev, vreg, true); @@ -4668,7 +5231,9 @@ static int ufshcd_disable_vreg(struct device *dev, struct ufs_vreg *vreg) { int ret = 0; - if (!vreg || !vreg->enabled) + if (!vreg) + goto out; + else if (!vreg->enabled || vreg->unused) goto out; ret = regulator_disable(vreg->reg); @@ -4774,6 +5339,36 @@ static int ufshcd_init_hba_vreg(struct ufs_hba *hba) return 0; } +static int ufshcd_set_vccq_rail_unused(struct ufs_hba *hba, bool unused) +{ + int ret = 0; + struct ufs_vreg_info *info = &hba->vreg_info; + + if (!info) + goto out; + else if (!info->vccq) + goto out; + + if (unused) { + /* shut off the rail here */ + ret = ufshcd_toggle_vreg(hba->dev, info->vccq, false); + /* + * Mark this rail as no longer used, so it doesn't get enabled + * later by mistake + */ + if (!ret) + info->vccq->unused = true; + } else { + /* + * rail should have been already enabled hence just make sure + * that unused flag is cleared. + */ + info->vccq->unused = false; + } +out: + return ret; +} + static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on, bool skip_ref_clk) { @@ -5093,10 +5688,20 @@ static int ufshcd_link_state_transition(struct ufs_hba *hba, (!check_for_bkops || (check_for_bkops && !hba->auto_bkops_enabled))) { /* + * Let's make sure that link is in low power mode, we are doing + * this currently by putting the link in Hibern8. Otherway to + * put the link in low power mode is to send the DME end point + * to device and then send the DME reset command to local + * unipro. But putting the link in hibern8 is much faster. + */ + ret = ufshcd_uic_hibern8_enter(hba); + if (ret) + goto out; + /* * Change controller state to "reset state" which * should also put the link in off/reset state */ - ufshcd_hba_stop(hba); + ufshcd_hba_stop(hba, true); /* * TODO: Check if we need any delay to make sure that * controller is reset @@ -5111,6 +5716,16 @@ out: static void ufshcd_vreg_set_lpm(struct ufs_hba *hba) { /* + * It seems some UFS devices may keep drawing more than sleep current + * (atleast for 500us) from UFS rails (especially from VCCQ rail). + * To avoid this situation, add 2ms delay before putting these UFS + * rails in LPM mode. + */ + if (!ufshcd_is_link_active(hba) && + hba->dev_quirks & UFS_DEVICE_QUIRK_DELAY_BEFORE_LPM) + usleep_range(2000, 2100); + + /* * If UFS device is either in UFS_Sleep turn off VCC rail to save some * power. * @@ -5572,7 +6187,7 @@ void ufshcd_remove(struct ufs_hba *hba) scsi_remove_host(hba->host); /* disable interrupts */ ufshcd_disable_intr(hba, hba->intr_mask); - ufshcd_hba_stop(hba); + ufshcd_hba_stop(hba, true); scsi_host_put(hba->host); @@ -5836,6 +6451,21 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) init_waitqueue_head(&hba->dev_cmd.tag_wq); ufshcd_init_clk_gating(hba); + + /* + * In order to avoid any spurious interrupt immediately after + * registering UFS controller interrupt handler, clear any pending UFS + * interrupt status and disable all the UFS interrupts. + */ + ufshcd_writel(hba, ufshcd_readl(hba, REG_INTERRUPT_STATUS), + REG_INTERRUPT_STATUS); + ufshcd_writel(hba, 0, REG_INTERRUPT_ENABLE); + /* + * Make sure that UFS interrupts are disabled and any pending interrupt + * status is cleared before registering UFS interrupt handler. + */ + mb(); + /* IRQ registration */ err = devm_request_irq(dev, irq, ufshcd_intr, IRQF_SHARED, UFSHCD, hba); if (err) { diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index e3931d0c94eb..4bb65669f052 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -54,6 +54,7 @@ #include <linux/clk.h> #include <linux/completion.h> #include <linux/regulator/consumer.h> +#include "unipro.h" #include <asm/irq.h> #include <asm/byteorder.h> @@ -383,6 +384,9 @@ struct ufs_init_prefetch { * @clk_list_head: UFS host controller clocks list node head * @pwr_info: holds current power mode * @max_pwr_info: keeps the device max valid pwm + * @urgent_bkops_lvl: keeps track of urgent bkops level for device + * @is_urgent_bkops_lvl_checked: keeps track if the urgent bkops level for + * device is known or not. */ struct ufs_hba { void __iomem *mmio_base; @@ -470,6 +474,9 @@ struct ufs_hba { unsigned int quirks; /* Deviations from standard UFSHCI spec. */ + /* Device deviations from standard UFS device spec. */ + unsigned int dev_quirks; + wait_queue_head_t tm_wq; wait_queue_head_t tm_tag_wq; unsigned long tm_condition; @@ -509,6 +516,8 @@ struct ufs_hba { bool wlun_dev_clr_ua; + /* Number of lanes available (1 or 2) for Rx/Tx */ + u32 lanes_per_direction; struct ufs_pa_layer_attr pwr_info; struct ufs_pwr_mode_info max_pwr_info; @@ -533,6 +542,9 @@ struct ufs_hba { struct devfreq *devfreq; struct ufs_clk_scaling clk_scaling; bool is_sys_suspended; + + enum bkops_status urgent_bkops_lvl; + bool is_urgent_bkops_lvl_checked; }; /* Returns true if clocks can be gated. Otherwise false */ @@ -588,15 +600,9 @@ int ufshcd_alloc_host(struct device *, struct ufs_hba **); void ufshcd_dealloc_host(struct ufs_hba *); int ufshcd_init(struct ufs_hba * , void __iomem * , unsigned int); void ufshcd_remove(struct ufs_hba *); - -/** - * ufshcd_hba_stop - Send controller to reset state - * @hba: per adapter instance - */ -static inline void ufshcd_hba_stop(struct ufs_hba *hba) -{ - ufshcd_writel(hba, CONTROLLER_DISABLE, REG_CONTROLLER_ENABLE); -} +int ufshcd_wait_for_register(struct ufs_hba *hba, u32 reg, u32 mask, + u32 val, unsigned long interval_us, + unsigned long timeout_ms, bool can_sleep); static inline void check_upiu_size(void) { @@ -682,11 +688,27 @@ static inline int ufshcd_dme_peer_get(struct ufs_hba *hba, return ufshcd_dme_get_attr(hba, attr_sel, mib_val, DME_PEER); } +int ufshcd_read_device_desc(struct ufs_hba *hba, u8 *buf, u32 size); + +static inline bool ufshcd_is_hs_mode(struct ufs_pa_layer_attr *pwr_info) +{ + return (pwr_info->pwr_rx == FAST_MODE || + pwr_info->pwr_rx == FASTAUTO_MODE) && + (pwr_info->pwr_tx == FAST_MODE || + pwr_info->pwr_tx == FASTAUTO_MODE); +} + +#define ASCII_STD true + +int ufshcd_read_string_desc(struct ufs_hba *hba, int desc_index, u8 *buf, + u32 size, bool ascii); + /* Expose Query-Request API */ int ufshcd_query_flag(struct ufs_hba *hba, enum query_opcode opcode, enum flag_idn idn, bool *flag_res); int ufshcd_hold(struct ufs_hba *hba, bool async); void ufshcd_release(struct ufs_hba *hba); +u32 ufshcd_get_local_unipro_ver(struct ufs_hba *hba); /* Wrapper functions for safely calling variant operations */ static inline const char *ufshcd_get_var_name(struct ufs_hba *hba) diff --git a/drivers/scsi/ufs/ufshci.h b/drivers/scsi/ufs/ufshci.h index 0ae0967aaed8..4cb1cc63f1a1 100644 --- a/drivers/scsi/ufs/ufshci.h +++ b/drivers/scsi/ufs/ufshci.h @@ -92,6 +92,7 @@ enum { UFSHCI_VERSION_10 = 0x00010000, /* 1.0 */ UFSHCI_VERSION_11 = 0x00010100, /* 1.1 */ UFSHCI_VERSION_20 = 0x00000200, /* 2.0 */ + UFSHCI_VERSION_21 = 0x00000210, /* 2.1 */ }; /* @@ -170,6 +171,8 @@ enum { #define UIC_DATA_LINK_LAYER_ERROR UFS_BIT(31) #define UIC_DATA_LINK_LAYER_ERROR_CODE_MASK 0x7FFF #define UIC_DATA_LINK_LAYER_ERROR_PA_INIT 0x2000 +#define UIC_DATA_LINK_LAYER_ERROR_NAC_RECEIVED 0x0001 +#define UIC_DATA_LINK_LAYER_ERROR_TCx_REPLAY_TIMEOUT 0x0002 /* UECN - Host UIC Error Code Network Layer 40h */ #define UIC_NETWORK_LAYER_ERROR UFS_BIT(31) @@ -209,6 +212,7 @@ enum { /* GenSelectorIndex calculation macros for M-PHY attributes */ #define UIC_ARG_MPHY_TX_GEN_SEL_INDEX(lane) (lane) +#define UIC_ARG_MPHY_RX_GEN_SEL_INDEX(lane) (PA_MAXDATALANES + (lane)) #define UIC_ARG_MIB_SEL(attr, sel) ((((attr) & 0xFFFF) << 16) |\ ((sel) & 0xFFFF)) diff --git a/drivers/scsi/ufs/unipro.h b/drivers/scsi/ufs/unipro.h index 816a8a46efb8..e2854e45f8d3 100644 --- a/drivers/scsi/ufs/unipro.h +++ b/drivers/scsi/ufs/unipro.h @@ -15,6 +15,7 @@ /* * M-TX Configuration Attributes */ +#define TX_HIBERN8TIME_CAPABILITY 0x000F #define TX_MODE 0x0021 #define TX_HSRATE_SERIES 0x0022 #define TX_HSGEAR 0x0023 @@ -48,8 +49,12 @@ #define RX_ENTER_HIBERN8 0x00A7 #define RX_BYPASS_8B10B_ENABLE 0x00A8 #define RX_TERMINATION_FORCE_ENABLE 0x0089 +#define RX_MIN_ACTIVATETIME_CAPABILITY 0x008F +#define RX_HIBERN8TIME_CAPABILITY 0x0092 #define is_mphy_tx_attr(attr) (attr < RX_MODE) +#define RX_MIN_ACTIVATETIME_UNIT_US 100 +#define HIBERN8TIME_UNIT_US 100 /* * PHY Adpater attributes */ @@ -70,6 +75,7 @@ #define PA_MAXRXSPEEDFAST 0x1541 #define PA_MAXRXSPEEDSLOW 0x1542 #define PA_TXLINKSTARTUPHS 0x1544 +#define PA_LOCAL_TX_LCC_ENABLE 0x155E #define PA_TXSPEEDFAST 0x1565 #define PA_TXSPEEDSLOW 0x1566 #define PA_REMOTEVERINFO 0x15A0 @@ -110,6 +116,12 @@ #define PA_STALLNOCONFIGTIME 0x15A3 #define PA_SAVECONFIGTIME 0x15A4 +#define PA_TACTIVATE_TIME_UNIT_US 10 +#define PA_HIBERN8_TIME_UNIT_US 100 + +/* PHY Adapter Protocol Constants */ +#define PA_MAXDATALANES 4 + /* PA power modes */ enum { FAST_MODE = 1, @@ -143,6 +155,16 @@ enum ufs_hs_gear_tag { UFS_HS_G3, /* HS Gear 3 */ }; +enum ufs_unipro_ver { + UFS_UNIPRO_VER_RESERVED = 0, + UFS_UNIPRO_VER_1_40 = 1, /* UniPro version 1.40 */ + UFS_UNIPRO_VER_1_41 = 2, /* UniPro version 1.41 */ + UFS_UNIPRO_VER_1_6 = 3, /* UniPro version 1.6 */ + UFS_UNIPRO_VER_MAX = 4, /* UniPro unsupported version */ + /* UniPro version field mask in PA_LOCALVERINFO */ + UFS_UNIPRO_VER_MASK = 0xF, +}; + /* * Data Link Layer Attributes */ |