scsi: ufs: Fix race condition in ufs qcom debugfs driver

In function ufs_qcom_dbg_testbus_cfg_write(), the global
variable ufs_qcom_host (host) is not protected by lock.
In function ufs_qcom_testbug_config(), we are checking this
variable in switch case and there is possibility of race
condition while accessing host variable in both of these
functions. This change fixes the possible race scenario
using spin_lock on host_lock.

Change-Id: I4e3fa1c3b80b92a648965371e12e52352cf80ce5
Signed-off-by: Sayali Lokhande <sayalil@codeaurora.org>
This commit is contained in:
Sayali Lokhande 2017-10-04 11:56:14 +05:30 committed by Gerrit - the friendly Code Review server
parent 2eaf14b921
commit a7bbc30ea3
3 changed files with 39 additions and 21 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, Linux Foundation. All rights reserved.
* Copyright (c) 2015,2017, 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
@ -110,6 +110,8 @@ static ssize_t ufs_qcom_dbg_testbus_cfg_write(struct file *file,
int ret = 0;
int major;
int minor;
unsigned long flags;
struct ufs_hba *hba = host->hba;
cnt = simple_write_to_buffer(configuration, TESTBUS_CFG_BUFF_LINE_SIZE,
&buff_pos, ubuf, cnt);
@ -135,8 +137,15 @@ static ssize_t ufs_qcom_dbg_testbus_cfg_write(struct file *file,
goto out;
}
if (!ufs_qcom_testbus_cfg_is_ok(host, major, minor)) {
ret = -EPERM;
goto out;
}
spin_lock_irqsave(hba->host->host_lock, flags);
host->testbus.select_major = (u8)major;
host->testbus.select_minor = (u8)minor;
spin_unlock_irqrestore(hba->host->host_lock, flags);
/*
* Sanity check of the {major, minor} tuple is done in the

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
* Copyright (c) 2013-2017, 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
@ -1488,12 +1488,13 @@ static void ufs_qcom_get_default_testbus_cfg(struct ufs_qcom_host *host)
host->testbus.select_minor = 1;
}
static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host,
u8 select_major, u8 select_minor)
{
if (host->testbus.select_major >= TSTBUS_MAX) {
if (select_major >= TSTBUS_MAX) {
dev_err(host->hba->dev,
"%s: UFS_CFG1[TEST_BUS_SEL} may not equal 0x%05X\n",
__func__, host->testbus.select_major);
__func__, select_major);
return false;
}
@ -1502,10 +1503,10 @@ static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
* mappings of select_minor, since there is no harm in
* configuring a non-existent select_minor
*/
if (host->testbus.select_major > 0x1F) {
if (select_minor > 0x1F) {
dev_err(host->hba->dev,
"%s: 0x%05X is not a legal testbus option\n",
__func__, host->testbus.select_minor);
__func__, select_minor);
return false;
}
@ -1514,16 +1515,16 @@ static bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host)
int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
{
int reg;
int offset;
int reg = 0;
int offset, ret = 0, testbus_sel_offset = 19;
u32 mask = TEST_BUS_SUB_SEL_MASK;
unsigned long flags;
struct ufs_hba *hba;
if (!host)
return -EINVAL;
if (!ufs_qcom_testbus_cfg_is_ok(host))
return -EPERM;
hba = host->hba;
spin_lock_irqsave(hba->host->host_lock, flags);
switch (host->testbus.select_major) {
case TSTBUS_UAWM:
reg = UFS_TEST_BUS_CTRL_0;
@ -1580,21 +1581,28 @@ int ufs_qcom_testbus_config(struct ufs_qcom_host *host)
*/
}
mask <<= offset;
spin_unlock_irqrestore(hba->host->host_lock, flags);
ufshcd_hold(host->hba, false);
pm_runtime_get_sync(host->hba->dev);
ufshcd_rmwl(host->hba, TEST_BUS_SEL,
(u32)host->testbus.select_major << 19,
if (reg) {
ufshcd_rmwl(host->hba, TEST_BUS_SEL,
(u32)host->testbus.select_major << testbus_sel_offset,
REG_UFS_CFG1);
ufshcd_rmwl(host->hba, mask,
ufshcd_rmwl(host->hba, mask,
(u32)host->testbus.select_minor << offset,
reg);
} else {
dev_err(hba->dev, "%s: Problem setting minor\n", __func__);
ret = -EINVAL;
goto out;
}
ufs_qcom_enable_test_bus(host);
out:
pm_runtime_put_sync(host->hba->dev);
ufshcd_release(host->hba, false);
return 0;
return ret;
}
static void ufs_qcom_testbus_read(struct ufs_hba *hba)

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2013-2015, The Linux Foundation. All rights reserved.
/* Copyright (c) 2013-2017, 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
@ -84,7 +84,7 @@ enum {
/* bit definitions for REG_UFS_CFG1 register */
#define TEST_BUS_EN BIT(18)
#define TEST_BUS_SEL GENMASK(22, 19)
#define TEST_BUS_SEL 0x780000
/* bit definitions for REG_UFS_CFG2 register */
#define UAWM_HW_CGC_EN (1 << 0)
@ -253,7 +253,8 @@ struct ufs_qcom_host {
#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)
bool ufs_qcom_testbus_cfg_is_ok(struct ufs_qcom_host *host,
u8 select_major, u8 select_minor);
int ufs_qcom_testbus_config(struct ufs_qcom_host *host);
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,