From bbe21d7a979245902113e171fdb36b57812b3c3b Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Wed, 2 May 2018 16:58:09 -0700 Subject: scsi: ufs: ufshcd: Remove VLA usage On the quest to remove all VLAs from the kernel[1] this moves buffers off the stack. In the second instance, this collapses two separately allocated buffers into a single buffer, since they are used consecutively, which saves 256 bytes (QUERY_DESC_MAX_SIZE + 1) of stack space. [1] https://lkml.kernel.org/r/CA+55aFzCG-zNmZwX4A2FQpadafLfEzK6CC=qPXydAacU1RqZWA@mail.gmail.com Signed-off-by: Kees Cook Reviewed-by: Subhash Jadavani Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index c5b1bf1cadcb..bbad34738bea 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -5918,14 +5918,18 @@ static void ufshcd_init_icc_levels(struct ufs_hba *hba) { int ret; int buff_len = hba->desc_size.pwr_desc; - u8 desc_buf[hba->desc_size.pwr_desc]; + u8 *desc_buf; + + desc_buf = kmalloc(buff_len, GFP_KERNEL); + if (!desc_buf) + return; ret = ufshcd_read_power_desc(hba, desc_buf, buff_len); if (ret) { dev_err(hba->dev, "%s: Failed reading power descriptor.len = %d ret = %d", __func__, buff_len, ret); - return; + goto out; } hba->init_prefetch_data.icc_level = @@ -5943,6 +5947,8 @@ static void ufshcd_init_icc_levels(struct ufs_hba *hba) "%s: Failed configuring bActiveICCLevel = %d ret = %d", __func__, hba->init_prefetch_data.icc_level , ret); +out: + kfree(desc_buf); } /** @@ -6012,9 +6018,17 @@ static int ufs_get_device_desc(struct ufs_hba *hba, struct ufs_dev_desc *dev_desc) { int err; + size_t buff_len; u8 model_index; - u8 str_desc_buf[QUERY_DESC_MAX_SIZE + 1] = {0}; - u8 desc_buf[hba->desc_size.dev_desc]; + u8 *desc_buf; + + buff_len = max_t(size_t, hba->desc_size.dev_desc, + QUERY_DESC_MAX_SIZE + 1); + desc_buf = kmalloc(buff_len, GFP_KERNEL); + if (!desc_buf) { + err = -ENOMEM; + goto out; + } err = ufshcd_read_device_desc(hba, desc_buf, hba->desc_size.dev_desc); if (err) { @@ -6032,7 +6046,10 @@ static int ufs_get_device_desc(struct ufs_hba *hba, model_index = desc_buf[DEVICE_DESC_PARAM_PRDCT_NAME]; - err = ufshcd_read_string_desc(hba, model_index, str_desc_buf, + /* Zero-pad entire buffer for string termination. */ + memset(desc_buf, 0, buff_len); + + err = ufshcd_read_string_desc(hba, model_index, desc_buf, QUERY_DESC_MAX_SIZE, true/*ASCII*/); if (err) { dev_err(hba->dev, "%s: Failed reading Product Name. err = %d\n", @@ -6040,15 +6057,16 @@ static int ufs_get_device_desc(struct ufs_hba *hba, goto out; } - str_desc_buf[QUERY_DESC_MAX_SIZE] = '\0'; - strlcpy(dev_desc->model, (str_desc_buf + QUERY_DESC_HDR_SIZE), - min_t(u8, str_desc_buf[QUERY_DESC_LENGTH_OFFSET], + desc_buf[QUERY_DESC_MAX_SIZE] = '\0'; + strlcpy(dev_desc->model, (desc_buf + QUERY_DESC_HDR_SIZE), + min_t(u8, desc_buf[QUERY_DESC_LENGTH_OFFSET], MAX_MODEL_LEN)); /* Null terminate the model string */ dev_desc->model[MAX_MODEL_LEN] = '\0'; out: + kfree(desc_buf); return err; } -- cgit v1.2.3 From 1399c5b02c568700ba50ce1ec19c12e71b25c73d Mon Sep 17 00:00:00 2001 From: Alim Akhtar Date: Sun, 6 May 2018 15:44:15 +0530 Subject: scsi: ufs: add quirk to fix mishandling utrlclr/utmrlclr In the right behavior, setting the bit to '0' indicates clear and '1' indicates no change. If host controller handles this the other way, UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR can be used. [mkp: typo] Signed-off-by: Seungwon Jeon Signed-off-by: Alim Akhtar Reviewed-by: Subhash Jadavani Reviewed-by: "Asutosh Das (asd)" Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 21 +++++++++++++++++++-- drivers/scsi/ufs/ufshcd.h | 5 +++++ 2 files changed, 24 insertions(+), 2 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index bbad34738bea..937355b04e4a 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -643,7 +643,24 @@ static inline void ufshcd_put_tm_slot(struct ufs_hba *hba, int slot) */ static inline void ufshcd_utrl_clear(struct ufs_hba *hba, u32 pos) { - ufshcd_writel(hba, ~(1 << pos), REG_UTP_TRANSFER_REQ_LIST_CLEAR); + if (hba->quirks & UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR) + ufshcd_writel(hba, (1 << pos), REG_UTP_TRANSFER_REQ_LIST_CLEAR); + else + ufshcd_writel(hba, ~(1 << pos), + REG_UTP_TRANSFER_REQ_LIST_CLEAR); +} + +/** + * ufshcd_utmrl_clear - Clear a bit in UTRMLCLR register + * @hba: per adapter instance + * @pos: position of the bit to be cleared + */ +static inline void ufshcd_utmrl_clear(struct ufs_hba *hba, u32 pos) +{ + if (hba->quirks & UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR) + ufshcd_writel(hba, (1 << pos), REG_UTP_TASK_REQ_LIST_CLEAR); + else + ufshcd_writel(hba, ~(1 << pos), REG_UTP_TASK_REQ_LIST_CLEAR); } /** @@ -5362,7 +5379,7 @@ static int ufshcd_clear_tm_cmd(struct ufs_hba *hba, int tag) goto out; spin_lock_irqsave(hba->host->host_lock, flags); - ufshcd_writel(hba, ~(1 << tag), REG_UTP_TASK_REQ_LIST_CLEAR); + ufshcd_utmrl_clear(hba, tag); spin_unlock_irqrestore(hba->host->host_lock, flags); /* poll for max. 1 sec to clear door bell register by h/w */ diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 8110dcd04d22..09c285f62c6a 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -595,6 +595,11 @@ struct ufs_hba { */ #define UFSHCD_QUIRK_PRDT_BYTE_GRAN 0x80 + /* + * Clear handling for transfer/task request list is just opposite. + */ + #define UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR 0x100 + unsigned int quirks; /* Deviations from standard UFSHCI spec. */ /* Device deviations from standard UFS device spec. */ -- cgit v1.2.3 From 5ac6abc9415c3c70040b529631426b78e26108c4 Mon Sep 17 00:00:00 2001 From: Alim Akhtar Date: Sun, 6 May 2018 15:44:16 +0530 Subject: scsi: ufs: add quirk to disallow reset of interrupt aggregation Some host controllers support interrupt aggregation but don't allow resetting counter and timer in software. Signed-off-by: Seungwon Jeon Signed-off-by: Alim Akhtar Reviewed-by: Subhash Jadavani Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 3 ++- drivers/scsi/ufs/ufshcd.h | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 937355b04e4a..1ef4d46b25c7 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -4659,7 +4659,8 @@ static void ufshcd_transfer_req_compl(struct ufs_hba *hba) * false interrupt if device completes another request after resetting * aggregation and before reading the DB. */ - if (ufshcd_is_intr_aggr_allowed(hba)) + if (ufshcd_is_intr_aggr_allowed(hba) && + !(hba->quirks & UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR)) ufshcd_reset_intr_aggr(hba); tr_doorbell = ufshcd_readl(hba, REG_UTP_TRANSFER_REQ_DOOR_BELL); diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 09c285f62c6a..d8c922d5bbee 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -600,6 +600,12 @@ struct ufs_hba { */ #define UFSHCI_QUIRK_BROKEN_REQ_LIST_CLR 0x100 + /* + * This quirk needs to be enabled if host controller doesn't allow + * that the interrupt aggregation timer and counter are reset by s/w. + */ + #define UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR 0x200 + unsigned int quirks; /* Deviations from standard UFSHCI spec. */ /* Device deviations from standard UFS device spec. */ -- cgit v1.2.3 From 4404c5de7707859d8e54f4356ac17c82413c80e9 Mon Sep 17 00:00:00 2001 From: Alim Akhtar Date: Sun, 6 May 2018 15:44:17 +0530 Subject: scsi: ufs: add quirk to enable host controller without hce Some host controllers don't support host controller enable via HCE. Signed-off-by: Seungwon Jeon Signed-off-by: Alim Akhtar Reviewed-by: Subhash Jadavani Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 75 +++++++++++++++++++++++++++++++++++++++++++++-- drivers/scsi/ufs/ufshcd.h | 5 ++++ 2 files changed, 78 insertions(+), 2 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 1ef4d46b25c7..bfb902380cf0 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -3364,6 +3364,52 @@ static int ufshcd_dme_link_startup(struct ufs_hba *hba) "dme-link-startup: error code %d\n", ret); return ret; } +/** + * ufshcd_dme_reset - UIC command for DME_RESET + * @hba: per adapter instance + * + * DME_RESET command is issued in order to reset UniPro stack. + * This function now deal with cold reset. + * + * Returns 0 on success, non-zero value on failure + */ +static int ufshcd_dme_reset(struct ufs_hba *hba) +{ + struct uic_command uic_cmd = {0}; + int ret; + + uic_cmd.command = UIC_CMD_DME_RESET; + + ret = ufshcd_send_uic_cmd(hba, &uic_cmd); + if (ret) + dev_err(hba->dev, + "dme-reset: error code %d\n", ret); + + return ret; +} + +/** + * ufshcd_dme_enable - UIC command for DME_ENABLE + * @hba: per adapter instance + * + * DME_ENABLE command is issued in order to enable UniPro stack. + * + * Returns 0 on success, non-zero value on failure + */ +static int ufshcd_dme_enable(struct ufs_hba *hba) +{ + struct uic_command uic_cmd = {0}; + int ret; + + uic_cmd.command = UIC_CMD_DME_ENABLE; + + ret = ufshcd_send_uic_cmd(hba, &uic_cmd); + if (ret) + dev_err(hba->dev, + "dme-reset: error code %d\n", ret); + + return ret; +} static inline void ufshcd_add_delay_before_dme_cmd(struct ufs_hba *hba) { @@ -4022,7 +4068,7 @@ static inline void ufshcd_hba_stop(struct ufs_hba *hba, bool can_sleep) } /** - * ufshcd_hba_enable - initialize the controller + * ufshcd_hba_execute_hce - initialize the controller * @hba: per adapter instance * * The controller resets itself and controller firmware initialization @@ -4031,7 +4077,7 @@ static inline void ufshcd_hba_stop(struct ufs_hba *hba, bool can_sleep) * * Returns 0 on success, non-zero value on failure */ -static int ufshcd_hba_enable(struct ufs_hba *hba) +static int ufshcd_hba_execute_hce(struct ufs_hba *hba) { int retry; @@ -4086,6 +4132,31 @@ static int ufshcd_hba_enable(struct ufs_hba *hba) return 0; } +static int ufshcd_hba_enable(struct ufs_hba *hba) +{ + int ret; + + if (hba->quirks & UFSHCI_QUIRK_BROKEN_HCE) { + ufshcd_set_link_off(hba); + ufshcd_vops_hce_enable_notify(hba, PRE_CHANGE); + + /* enable UIC related interrupts */ + ufshcd_enable_intr(hba, UFSHCD_UIC_MASK); + ret = ufshcd_dme_reset(hba); + if (!ret) { + ret = ufshcd_dme_enable(hba); + if (!ret) + ufshcd_vops_hce_enable_notify(hba, POST_CHANGE); + if (ret) + dev_err(hba->dev, + "Host controller enable failed with non-hce\n"); + } + } else { + ret = ufshcd_hba_execute_hce(hba); + } + + return ret; +} static int ufshcd_disable_tx_lcc(struct ufs_hba *hba, bool peer) { int tx_lanes, i, err = 0; diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index d8c922d5bbee..a39cdb51ab41 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -606,6 +606,11 @@ struct ufs_hba { */ #define UFSHCI_QUIRK_SKIP_RESET_INTR_AGGR 0x200 + /* + * This quirks needs to be enabled if host controller cannot be + * enabled via HCE register. + */ + #define UFSHCI_QUIRK_BROKEN_HCE 0x400 unsigned int quirks; /* Deviations from standard UFSHCI spec. */ /* Device deviations from standard UFS device spec. */ -- cgit v1.2.3 From 0d846e703dc8b77d31cc71e0bd8d004f187eac30 Mon Sep 17 00:00:00 2001 From: Alim Akhtar Date: Sun, 6 May 2018 15:44:18 +0530 Subject: scsi: ufs: make ufshcd_config_pwr_mode of non-static func This makes ufshcd_config_pwr_mode non-static so that other vendors like exynos can use it. Signed-off-by: Seungwon Jeon Signed-off-by: Alim Akhtar Reviewed-by: Avri Altman Reviewed-by: Subhash Jadavani Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 5 ++--- drivers/scsi/ufs/ufshcd.h | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index bfb902380cf0..134e99e9f789 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -233,8 +233,6 @@ static void ufshcd_suspend_clkscaling(struct ufs_hba *hba); static void __ufshcd_suspend_clkscaling(struct ufs_hba *hba); static int ufshcd_scale_clks(struct ufs_hba *hba, bool scale_up); static irqreturn_t ufshcd_intr(int irq, void *__hba); -static int ufshcd_config_pwr_mode(struct ufs_hba *hba, - struct ufs_pa_layer_attr *desired_pwr_mode); static int ufshcd_change_power_mode(struct ufs_hba *hba, struct ufs_pa_layer_attr *pwr_mode); static inline bool ufshcd_valid_tag(struct ufs_hba *hba, int tag) @@ -3933,7 +3931,7 @@ static int ufshcd_change_power_mode(struct ufs_hba *hba, * @hba: per-adapter instance * @desired_pwr_mode: desired power configuration */ -static int ufshcd_config_pwr_mode(struct ufs_hba *hba, +int ufshcd_config_pwr_mode(struct ufs_hba *hba, struct ufs_pa_layer_attr *desired_pwr_mode) { struct ufs_pa_layer_attr final_params = { 0 }; @@ -3951,6 +3949,7 @@ static int ufshcd_config_pwr_mode(struct ufs_hba *hba, return ret; } +EXPORT_SYMBOL_GPL(ufshcd_config_pwr_mode); /** * ufshcd_complete_dev_init() - checks device readiness diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index a39cdb51ab41..53121fd6c287 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -805,6 +805,8 @@ extern int ufshcd_dme_set_attr(struct ufs_hba *hba, u32 attr_sel, u8 attr_set, u32 mib_val, u8 peer); extern int ufshcd_dme_get_attr(struct ufs_hba *hba, u32 attr_sel, u32 *mib_val, u8 peer); +extern int ufshcd_config_pwr_mode(struct ufs_hba *hba, + struct ufs_pa_layer_attr *desired_pwr_mode); /* UIC command interfaces for DME primitives */ #define DME_LOCAL 0 -- cgit v1.2.3 From deac444f4e44c406796a086381db1c09c70ce042 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 17 May 2018 23:26:36 -0700 Subject: scsi: ufs: Extract devfreq registration Failing to register with devfreq leaves hba->devfreq assigned, which causes the error path to dereference the ERR_PTR(). Rather than bolting on more conditionals, move the call of devm_devfreq_add_device() into it's own function and only update hba->devfreq once it's successfully registered. The subsequent patch builds upon this to make UFS actually work again, as it's been broken since f1d981eaecf8 ("PM / devfreq: Use the available min/max frequency") Also switch to use DEVFREQ_GOV_SIMPLE_ONDEMAND constant. Reviewed-by: Chanwoo Choi Signed-off-by: Bjorn Andersson Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 134e99e9f789..c21c9bd18073 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -1270,6 +1270,26 @@ static struct devfreq_dev_profile ufs_devfreq_profile = { .get_dev_status = ufshcd_devfreq_get_dev_status, }; +static int ufshcd_devfreq_init(struct ufs_hba *hba) +{ + struct devfreq *devfreq; + int ret; + + devfreq = devm_devfreq_add_device(hba->dev, + &ufs_devfreq_profile, + DEVFREQ_GOV_SIMPLE_ONDEMAND, + NULL); + if (IS_ERR(devfreq)) { + ret = PTR_ERR(devfreq); + dev_err(hba->dev, "Unable to register with devfreq %d\n", ret); + return ret; + } + + hba->devfreq = devfreq; + + return 0; +} + static void __ufshcd_suspend_clkscaling(struct ufs_hba *hba) { unsigned long flags; @@ -6505,16 +6525,9 @@ static int ufshcd_probe_hba(struct ufs_hba *hba) sizeof(struct ufs_pa_layer_attr)); hba->clk_scaling.saved_pwr_info.is_valid = true; if (!hba->devfreq) { - hba->devfreq = devm_devfreq_add_device(hba->dev, - &ufs_devfreq_profile, - "simple_ondemand", - NULL); - if (IS_ERR(hba->devfreq)) { - ret = PTR_ERR(hba->devfreq); - dev_err(hba->dev, "Unable to register with devfreq %d\n", - ret); + ret = ufshcd_devfreq_init(hba); + if (ret) goto out; - } } hba->clk_scaling.is_allowed = true; } -- cgit v1.2.3 From 092b45583c524edac688f0b6cdc62a70c3081e4a Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 17 May 2018 23:26:37 -0700 Subject: scsi: ufs: Use freq table with devfreq devfreq requires that the client operates on actual frequencies, not only 0 and UMAX_INT and as such UFS brok with the introduction of f1d981eaecf8 ("PM / devfreq: Use the available min/max frequency"). This patch registers the frequencies of the first clock with devfreq and use these to determine if we're trying to step up or down. Reviewed-by: Chanwoo Choi [for devfreq & OPP part] Reviewed-by: Subhash Jadavani Signed-off-by: Bjorn Andersson Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index c21c9bd18073..892ad43dac9a 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -1183,16 +1183,13 @@ static int ufshcd_devfreq_target(struct device *dev, struct ufs_hba *hba = dev_get_drvdata(dev); ktime_t start; bool scale_up, sched_clk_scaling_suspend_work = false; + struct list_head *clk_list = &hba->clk_list_head; + struct ufs_clk_info *clki; unsigned long irq_flags; if (!ufshcd_is_clkscaling_supported(hba)) return -EINVAL; - if ((*freq > 0) && (*freq < UINT_MAX)) { - dev_err(hba->dev, "%s: invalid freq = %lu\n", __func__, *freq); - return -EINVAL; - } - spin_lock_irqsave(hba->host->host_lock, irq_flags); if (ufshcd_eh_in_progress(hba)) { spin_unlock_irqrestore(hba->host->host_lock, irq_flags); @@ -1202,7 +1199,13 @@ static int ufshcd_devfreq_target(struct device *dev, if (!hba->clk_scaling.active_reqs) sched_clk_scaling_suspend_work = true; - scale_up = (*freq == UINT_MAX) ? true : false; + if (list_empty(clk_list)) { + spin_unlock_irqrestore(hba->host->host_lock, irq_flags); + goto out; + } + + clki = list_first_entry(&hba->clk_list_head, struct ufs_clk_info, list); + scale_up = (*freq == clki->max_freq) ? true : false; if (!ufshcd_is_devfreq_scaling_required(hba, scale_up)) { spin_unlock_irqrestore(hba->host->host_lock, irq_flags); ret = 0; @@ -1272,16 +1275,29 @@ static struct devfreq_dev_profile ufs_devfreq_profile = { static int ufshcd_devfreq_init(struct ufs_hba *hba) { + struct list_head *clk_list = &hba->clk_list_head; + struct ufs_clk_info *clki; struct devfreq *devfreq; int ret; - devfreq = devm_devfreq_add_device(hba->dev, + /* Skip devfreq if we don't have any clocks in the list */ + if (list_empty(clk_list)) + return 0; + + clki = list_first_entry(clk_list, struct ufs_clk_info, list); + dev_pm_opp_add(hba->dev, clki->min_freq, 0); + dev_pm_opp_add(hba->dev, clki->max_freq, 0); + + devfreq = devfreq_add_device(hba->dev, &ufs_devfreq_profile, DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL); if (IS_ERR(devfreq)) { ret = PTR_ERR(devfreq); dev_err(hba->dev, "Unable to register with devfreq %d\n", ret); + + dev_pm_opp_remove(hba->dev, clki->min_freq); + dev_pm_opp_remove(hba->dev, clki->max_freq); return ret; } @@ -1290,6 +1306,22 @@ static int ufshcd_devfreq_init(struct ufs_hba *hba) return 0; } +static void ufshcd_devfreq_remove(struct ufs_hba *hba) +{ + struct list_head *clk_list = &hba->clk_list_head; + struct ufs_clk_info *clki; + + if (!hba->devfreq) + return; + + devfreq_remove_device(hba->devfreq); + hba->devfreq = NULL; + + clki = list_first_entry(clk_list, struct ufs_clk_info, list); + dev_pm_opp_remove(hba->dev, clki->min_freq); + dev_pm_opp_remove(hba->dev, clki->max_freq); +} + static void __ufshcd_suspend_clkscaling(struct ufs_hba *hba) { unsigned long flags; @@ -7071,6 +7103,7 @@ static void ufshcd_hba_exit(struct ufs_hba *hba) if (hba->devfreq) ufshcd_suspend_clkscaling(hba); destroy_workqueue(hba->clk_scaling.workq); + ufshcd_devfreq_remove(hba); } ufshcd_setup_clocks(hba, false); ufshcd_setup_hba_vreg(hba, false); -- cgit v1.2.3 From 2e3611e9546c2ed4def152a51dfd34e8dddae7a5 Mon Sep 17 00:00:00 2001 From: Maya Erez Date: Thu, 3 May 2018 16:37:16 +0530 Subject: scsi: ufs: fix exception event handling The device can set the exception event bit in one of the response UPIU, for example to notify the need for urgent BKOPs operation. In such a case, the host driver calls ufshcd_exception_event_handler to handle this notification. When trying to check the exception event status (for finding the cause for the exception event), the device may be busy with additional SCSI commands handling and may not respond within the 100ms timeout. To prevent that, we need to block SCSI commands during handling of exception events and allow retransmissions of the query requests, in case of timeout. Signed-off-by: Subhash Jadavani Signed-off-by: Maya Erez Signed-off-by: Can Guo Signed-off-by: Asutosh Das Reviewed-by: Subhash Jadavani Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 892ad43dac9a..4dc48804c17b 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -5073,6 +5073,7 @@ static void ufshcd_exception_event_handler(struct work_struct *work) hba = container_of(work, struct ufs_hba, eeh_work); pm_runtime_get_sync(hba->dev); + scsi_block_requests(hba->host); err = ufshcd_get_ee_status(hba, &status); if (err) { dev_err(hba->dev, "%s: failed to get exception status %d\n", @@ -5086,6 +5087,7 @@ static void ufshcd_exception_event_handler(struct work_struct *work) ufshcd_bkops_exception_event_handler(hba); out: + scsi_unblock_requests(hba->host); pm_runtime_put_sync(hba->dev); return; } -- cgit v1.2.3 From b334456ec2021b1addc19806990115e69ec4ac32 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 3 May 2018 16:37:17 +0530 Subject: scsi: ufs: ufshcd: fix possible unclocked register access Vendor specific setup_clocks ops may depend on clocks managed by ufshcd driver so if the vendor specific setup_clocks callback is called when the required clocks are turned off, it results into unclocked register access. This change make sure that required clocks are enabled before vendor specific setup_clocks callback is called. Signed-off-by: Subhash Jadavani Signed-off-by: Venkat Gopalakrishnan Signed-off-by: Can Guo Signed-off-by: Asutosh Das Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 4dc48804c17b..79591934ef45 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -6912,9 +6912,16 @@ static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on, if (list_empty(head)) goto out; - ret = ufshcd_vops_setup_clocks(hba, on, PRE_CHANGE); - if (ret) - return ret; + /* + * vendor specific setup_clocks ops may depend on clocks managed by + * this standard driver hence call the vendor specific setup_clocks + * before disabling the clocks managed here. + */ + if (!on) { + ret = ufshcd_vops_setup_clocks(hba, on, PRE_CHANGE); + if (ret) + return ret; + } list_for_each_entry(clki, head, list) { if (!IS_ERR_OR_NULL(clki->clk)) { @@ -6938,9 +6945,16 @@ static int __ufshcd_setup_clocks(struct ufs_hba *hba, bool on, } } - ret = ufshcd_vops_setup_clocks(hba, on, POST_CHANGE); - if (ret) - return ret; + /* + * vendor specific setup_clocks ops may depend on clocks managed by + * this standard driver hence call the vendor specific setup_clocks + * after enabling the clocks managed here. + */ + if (on) { + ret = ufshcd_vops_setup_clocks(hba, on, POST_CHANGE); + if (ret) + return ret; + } out: if (ret) { -- cgit v1.2.3 From 38135535dcc25af856336fda31aeef79d8ad9dab Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 3 May 2018 16:37:18 +0530 Subject: scsi: ufs: add reference counting for scsi block requests Currently we call the scsi_block_requests()/scsi_unblock_requests() whenever we want to block/unblock scsi requests but as there is no reference counting, nesting of these calls could leave us in undesired state sometime. Consider following call flow sequence: 1. func1() calls scsi_block_requests() but calls func2() before calling scsi_unblock_requests() 2. func2() calls scsi_block_requests() 3. func2() calls scsi_unblock_requests() 4. func1() calls scsi_unblock_requests() As there is no reference counting, we will have scsi requests unblocked after #3 instead of it to be unblocked only after #4. Though we may not have failures seen with this, we might run into some failures in future. Better solution would be to fix this by adding reference counting. Signed-off-by: Subhash Jadavani Signed-off-by: Can Guo Signed-off-by: Asutosh Das Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 28 ++++++++++++++++++++-------- drivers/scsi/ufs/ufshcd.h | 2 ++ 2 files changed, 22 insertions(+), 8 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 79591934ef45..2859f6395eea 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -264,6 +264,18 @@ static inline void ufshcd_disable_irq(struct ufs_hba *hba) } } +static void ufshcd_scsi_unblock_requests(struct ufs_hba *hba) +{ + if (atomic_dec_and_test(&hba->scsi_block_reqs_cnt)) + scsi_unblock_requests(hba->host); +} + +static void ufshcd_scsi_block_requests(struct ufs_hba *hba) +{ + if (atomic_inc_return(&hba->scsi_block_reqs_cnt) == 1) + scsi_block_requests(hba->host); +} + /* replace non-printable or non-ASCII characters with spaces */ static inline void ufshcd_remove_non_printable(char *val) { @@ -1074,12 +1086,12 @@ static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba) * make sure that there are no outstanding requests when * clock scaling is in progress */ - scsi_block_requests(hba->host); + ufshcd_scsi_block_requests(hba); down_write(&hba->clk_scaling_lock); if (ufshcd_wait_for_doorbell_clr(hba, DOORBELL_CLR_TOUT_US)) { ret = -EBUSY; up_write(&hba->clk_scaling_lock); - scsi_unblock_requests(hba->host); + ufshcd_scsi_unblock_requests(hba); } return ret; @@ -1088,7 +1100,7 @@ static int ufshcd_clock_scaling_prepare(struct ufs_hba *hba) static void ufshcd_clock_scaling_unprepare(struct ufs_hba *hba) { up_write(&hba->clk_scaling_lock); - scsi_unblock_requests(hba->host); + ufshcd_scsi_unblock_requests(hba); } /** @@ -1460,7 +1472,7 @@ static void ufshcd_ungate_work(struct work_struct *work) hba->clk_gating.is_suspended = false; } unblock_reqs: - scsi_unblock_requests(hba->host); + ufshcd_scsi_unblock_requests(hba); } /** @@ -1516,7 +1528,7 @@ start: * work and to enable clocks. */ case CLKS_OFF: - scsi_block_requests(hba->host); + ufshcd_scsi_block_requests(hba); hba->clk_gating.state = REQ_CLKS_ON; trace_ufshcd_clk_gating(dev_name(hba->dev), hba->clk_gating.state); @@ -5298,7 +5310,7 @@ skip_err_handling: out: spin_unlock_irqrestore(hba->host->host_lock, flags); - scsi_unblock_requests(hba->host); + ufshcd_scsi_unblock_requests(hba); ufshcd_release(hba); pm_runtime_put_sync(hba->dev); } @@ -5400,7 +5412,7 @@ static void ufshcd_check_errors(struct ufs_hba *hba) /* 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); + ufshcd_scsi_block_requests(hba); hba->ufshcd_state = UFSHCD_STATE_EH_SCHEDULED; @@ -8032,7 +8044,7 @@ int ufshcd_init(struct ufs_hba *hba, void __iomem *mmio_base, unsigned int irq) /* Hold auto suspend until async scan completes */ pm_runtime_get_sync(dev); - + atomic_set(&hba->scsi_block_reqs_cnt, 0); /* * We are assuming that device wasn't put in sleep/power-down * state exclusively during the boot stage before kernel. diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index 53121fd6c287..a44b9f45ddd2 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -499,6 +499,7 @@ struct ufs_stats { * @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. + * @scsi_block_reqs_cnt: reference counting for scsi block requests */ struct ufs_hba { void __iomem *mmio_base; @@ -699,6 +700,7 @@ struct ufs_hba { struct rw_semaphore clk_scaling_lock; struct ufs_desc_size desc_size; + atomic_t scsi_block_reqs_cnt; }; /* Returns true if clocks can be gated. Otherwise false */ -- cgit v1.2.3 From 69a6fff068567469c0ef1156ae5ac8d3d71701f0 Mon Sep 17 00:00:00 2001 From: Subhash Jadavani Date: Thu, 3 May 2018 16:37:19 +0530 Subject: scsi: ufs: ufs-qcom: remove broken hci version quirk UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION is only applicable for QCOM UFS host controller version 2.x.y and this has been fixed from version 3.x.y onwards, hence this change removes this quirk for version 3.x.y onwards. [mkp: applied by hand] Signed-off-by: Subhash Jadavani Signed-off-by: Asutosh Das Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufs-qcom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufs-qcom.c b/drivers/scsi/ufs/ufs-qcom.c index 2b38db2eeafa..221820a7c78b 100644 --- a/drivers/scsi/ufs/ufs-qcom.c +++ b/drivers/scsi/ufs/ufs-qcom.c @@ -1098,7 +1098,7 @@ static void ufs_qcom_advertise_quirks(struct ufs_hba *hba) hba->quirks |= UFSHCD_QUIRK_BROKEN_LCC; } - if (host->hw_ver.major >= 0x2) { + if (host->hw_ver.major == 0x2) { hba->quirks |= UFSHCD_QUIRK_BROKEN_UFS_HCI_VERSION; if (!ufs_qcom_cap_qunipro(host)) -- cgit v1.2.3 From 7f6ba4f12e6cbfdefbb95cfd8fc67ece6c15d799 Mon Sep 17 00:00:00 2001 From: Venkat Gopalakrishnan Date: Thu, 3 May 2018 16:37:20 +0530 Subject: scsi: ufs: make sure all interrupts are processed As multiple requests are submitted to the ufs host controller in parallel there could be instances where the command completion interrupt arrives later for a request that is already processed earlier as the corresponding doorbell was cleared when handling the previous interrupt. Read the interrupt status in a loop after processing the received interrupt to catch such interrupts and handle it. Signed-off-by: Venkat Gopalakrishnan Signed-off-by: Asutosh Das Reviewed-by: Subhash Jadavani Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 2859f6395eea..028492179101 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -5489,19 +5489,30 @@ static irqreturn_t ufshcd_intr(int irq, void *__hba) u32 intr_status, enabled_intr_status; irqreturn_t retval = IRQ_NONE; struct ufs_hba *hba = __hba; + int retries = hba->nutrs; spin_lock(hba->host->host_lock); intr_status = ufshcd_readl(hba, REG_INTERRUPT_STATUS); - enabled_intr_status = - intr_status & ufshcd_readl(hba, REG_INTERRUPT_ENABLE); - if (intr_status) - ufshcd_writel(hba, intr_status, REG_INTERRUPT_STATUS); + /* + * There could be max of hba->nutrs reqs in flight and in worst case + * if the reqs get finished 1 by 1 after the interrupt status is + * read, make sure we handle them by checking the interrupt status + * again in a loop until we process all of the reqs before returning. + */ + do { + enabled_intr_status = + intr_status & ufshcd_readl(hba, REG_INTERRUPT_ENABLE); + if (intr_status) + ufshcd_writel(hba, intr_status, REG_INTERRUPT_STATUS); + if (enabled_intr_status) { + ufshcd_sl_intr(hba, enabled_intr_status); + retval = IRQ_HANDLED; + } + + intr_status = ufshcd_readl(hba, REG_INTERRUPT_STATUS); + } while (intr_status && --retries); - if (enabled_intr_status) { - ufshcd_sl_intr(hba, enabled_intr_status); - retval = IRQ_HANDLED; - } spin_unlock(hba->host->host_lock); return retval; } -- cgit v1.2.3 From 10e5e37581fc5817d52ff5f8e2b3362f64eb04f4 Mon Sep 17 00:00:00 2001 From: Vijay Viswanath Date: Thu, 3 May 2018 16:37:22 +0530 Subject: scsi: ufs: Add clock ungating to a separate workqueue UFS driver can receive a request during memory reclaim by kswapd. So when ufs driver puts the ungate work in queue, and if there are no idle workers, kthreadd is invoked to create a new kworker. Since kswapd task holds a mutex which kthreadd also needs, this can cause a deadlock situation. So ungate work must be done in a separate work queue with WQ_MEM_RECLAIM flag enabled. Such a workqueue will have a rescue thread which will be called when the above deadlock condition is possible. Signed-off-by: Vijay Viswanath Signed-off-by: Can Guo Signed-off-by: Asutosh Das Reviewed-by: Subhash Jadavani Signed-off-by: Martin K. Petersen --- drivers/scsi/ufs/ufshcd.c | 11 ++++++++++- drivers/scsi/ufs/ufshcd.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'drivers/scsi/ufs') diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index 028492179101..77e2b3e1f8a2 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -1532,7 +1532,8 @@ start: hba->clk_gating.state = REQ_CLKS_ON; trace_ufshcd_clk_gating(dev_name(hba->dev), hba->clk_gating.state); - schedule_work(&hba->clk_gating.ungate_work); + queue_work(hba->clk_gating.clk_gating_workq, + &hba->clk_gating.ungate_work); /* * fall through to check if we should wait for this * work to be done or not. @@ -1718,6 +1719,8 @@ out: static void ufshcd_init_clk_gating(struct ufs_hba *hba) { + char wq_name[sizeof("ufs_clk_gating_00")]; + if (!ufshcd_is_clkgating_allowed(hba)) return; @@ -1725,6 +1728,11 @@ static void ufshcd_init_clk_gating(struct ufs_hba *hba) INIT_DELAYED_WORK(&hba->clk_gating.gate_work, ufshcd_gate_work); INIT_WORK(&hba->clk_gating.ungate_work, ufshcd_ungate_work); + snprintf(wq_name, ARRAY_SIZE(wq_name), "ufs_clk_gating_%d", + hba->host->host_no); + hba->clk_gating.clk_gating_workq = alloc_ordered_workqueue(wq_name, + WQ_MEM_RECLAIM); + hba->clk_gating.is_enabled = true; hba->clk_gating.delay_attr.show = ufshcd_clkgate_delay_show; @@ -1752,6 +1760,7 @@ static void ufshcd_exit_clk_gating(struct ufs_hba *hba) device_remove_file(hba->dev, &hba->clk_gating.enable_attr); cancel_work_sync(&hba->clk_gating.ungate_work); cancel_delayed_work_sync(&hba->clk_gating.gate_work); + destroy_workqueue(hba->clk_gating.clk_gating_workq); } /* Must be called with host lock acquired */ diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h index a44b9f45ddd2..f51758f1e5cc 100644 --- a/drivers/scsi/ufs/ufshcd.h +++ b/drivers/scsi/ufs/ufshcd.h @@ -362,6 +362,7 @@ struct ufs_clk_gating { struct device_attribute enable_attr; bool is_enabled; int active_reqs; + struct workqueue_struct *clk_gating_workq; }; struct ufs_saved_pwr_info { -- cgit v1.2.3