@@ -17,6 +17,7 @@
*
*/
+#include <linux/random.h>
#include "ufs-debugfs.h"
#include "unipro.h"
@@ -41,6 +42,143 @@ struct desc_field_offset {
} while (0)
#define DOORBELL_CLR_TOUT_US (1000 * 1000) /* 1 sec */
+#ifdef CONFIG_UFS_FAULT_INJECTION
+
+#define INJECT_COMMAND_HANG (0x0)
+
+static DECLARE_FAULT_ATTR(fail_default_attr);
+static char *fail_request;
+module_param(fail_request, charp, 0);
+
+static bool inject_fatal_err_tr(struct ufs_hba *hba, u8 ocs_err)
+{
+ int tag;
+
+ tag = find_first_bit(&hba->outstanding_reqs, hba->nutrs);
+ if (tag == hba->nutrs)
+ return 0;
+
+ ufshcd_writel(hba, ~(1 << tag), REG_UTP_TRANSFER_REQ_LIST_CLEAR);
+ (&hba->lrb[tag])->utr_descriptor_ptr->header.dword_2 =
+ cpu_to_be32(ocs_err);
+
+ /* fatal error injected */
+ return 1;
+}
+
+static bool inject_fatal_err_tm(struct ufs_hba *hba, u8 ocs_err)
+{
+ int tag;
+
+ tag = find_first_bit(&hba->outstanding_tasks, hba->nutmrs);
+ if (tag == hba->nutmrs)
+ return 0;
+
+ ufshcd_writel(hba, ~(1 << tag), REG_UTP_TASK_REQ_LIST_CLEAR);
+ (&hba->utmrdl_base_addr[tag])->header.dword_2 =
+ cpu_to_be32(ocs_err);
+
+ /* fatal error injected */
+ return 1;
+}
+
+static bool inject_cmd_hang_tr(struct ufs_hba *hba)
+{
+ int tag;
+
+ tag = find_first_bit(&hba->outstanding_reqs, hba->nutrs);
+ if (tag == hba->nutrs)
+ return 0;
+
+ __clear_bit(tag, &hba->outstanding_reqs);
+ hba->lrb[tag].cmd = NULL;
+ __clear_bit(tag, &hba->lrb_in_use);
+
+ /* command hang injected */
+ return 1;
+}
+
+static int inject_cmd_hang_tm(struct ufs_hba *hba)
+{
+ int tag;
+
+ tag = find_first_bit(&hba->outstanding_tasks, hba->nutmrs);
+ if (tag == hba->nutmrs)
+ return 0;
+
+ __clear_bit(tag, &hba->outstanding_tasks);
+ __clear_bit(tag, &hba->tm_slots_in_use);
+
+ /* command hang injected */
+ return 1;
+}
+
+void ufsdbg_fail_request(struct ufs_hba *hba, u32 *intr_status)
+{
+ u8 ocs_err;
+ static const u32 errors[] = {
+ CONTROLLER_FATAL_ERROR,
+ SYSTEM_BUS_FATAL_ERROR,
+ INJECT_COMMAND_HANG,
+ };
+
+ if (!should_fail(&hba->debugfs_files.fail_attr, 1))
+ goto out;
+
+ *intr_status = errors[prandom_u32() % ARRAY_SIZE(errors)];
+ dev_info(hba->dev, "%s: fault-inject error: 0x%x\n",
+ __func__, *intr_status);
+
+ switch (*intr_status) {
+ case CONTROLLER_FATAL_ERROR: /* fall through */
+ ocs_err = OCS_FATAL_ERROR;
+ goto set_ocs;
+ case SYSTEM_BUS_FATAL_ERROR:
+ ocs_err = OCS_INVALID_CMD_TABLE_ATTR;
+set_ocs:
+ if (!inject_fatal_err_tr(hba, ocs_err))
+ if (!inject_fatal_err_tm(hba, ocs_err))
+ *intr_status = 0;
+ break;
+ case INJECT_COMMAND_HANG:
+ if (!inject_cmd_hang_tr(hba))
+ inject_cmd_hang_tm(hba);
+ break;
+ default:
+ BUG();
+ /* some configurations ignore panics caused by BUG() */
+ break;
+ }
+out:
+ return;
+}
+
+static void ufsdbg_setup_fault_injection(struct ufs_hba *hba)
+{
+ hba->debugfs_files.fail_attr = fail_default_attr;
+
+ if (fail_request)
+ setup_fault_attr(&hba->debugfs_files.fail_attr, fail_request);
+
+ /* suppress dump stack everytime failure is injected */
+ hba->debugfs_files.fail_attr.verbose = 0;
+
+ if (IS_ERR(fault_create_debugfs_attr("inject_fault",
+ hba->debugfs_files.debugfs_root,
+ &hba->debugfs_files.fail_attr)))
+ dev_err(hba->dev, "%s: failed to create debugfs entry\n",
+ __func__);
+}
+#else
+void ufsdbg_fail_request(struct ufs_hba *hba, u32 *intr_status)
+{
+}
+
+static void ufsdbg_setup_fault_injection(struct ufs_hba *hba)
+{
+}
+#endif /* CONFIG_UFS_FAULT_INJECTION */
+
#define BUFF_LINE_CAPACITY 16
#define TAB_CHARS 8
@@ -885,6 +1023,8 @@ void ufsdbg_add_debugfs(struct ufs_hba *hba)
goto err;
}
+ ufsdbg_setup_fault_injection(hba);
+
return;
err:
@@ -27,6 +27,16 @@
void ufsdbg_add_debugfs(struct ufs_hba *hba);
void ufsdbg_remove_debugfs(struct ufs_hba *hba);
+void ufsdbg_fail_request(struct ufs_hba *hba, u32 *intr_status);
+#else
+void ufsdbg_add_debugfs(struct ufs_hba *hba)
+{
+}
+void ufsdbg_remove_debugfs(struct ufs_hba *hba)
+{
+}
+void ufsdbg_fail_request(struct ufs_hba *hba, u32 *intr_status)
+{
+}
#endif
-
#endif /* End of Header */
@@ -4009,6 +4009,8 @@ static void ufshcd_tmc_handler(struct ufs_hba *hba)
*/
static void ufshcd_sl_intr(struct ufs_hba *hba, u32 intr_status)
{
+ ufsdbg_fail_request(hba, &intr_status);
+
hba->errors = UFSHCD_ERROR_MASK & intr_status;
if (hba->errors)
ufshcd_check_errors(hba);
@@ -64,6 +64,8 @@
#include <scsi/scsi_dbg.h>
#include <scsi/scsi_eh.h>
+#include <linux/fault-inject.h>
+
#include "ufs.h"
#include "ufshci.h"
@@ -283,6 +285,9 @@ struct debugfs_files {
struct dentry *dme_peer_read;
u32 dme_local_attr_id;
u32 dme_peer_attr_id;
+#ifdef CONFIG_UFS_FAULT_INJECTION
+ struct fault_attr fail_attr;
+#endif
};
/* tag stats statistics types */
@@ -1432,6 +1432,20 @@ config FAIL_MMC_REQUEST
and to test how the mmc host driver handles retries from
the block device.
+config UFS_FAULT_INJECTION
+ bool "Fault-injection capability for UFS IO"
+ select DEBUG_FS
+ depends on FAULT_INJECTION && SCSI_UFSHCD
+ help
+ Provide fault-injection capability for UFS IO.
+ This will make the UFS host controller driver to randomly
+ abort ongoing commands in the host controller, update OCS
+ field according to the injected fatal error and can also
+ forcefully hang the command indefinitely till upper layer
+ timeout occurs. This is useful to test error handling in
+ the UFS contoller driver and test how the driver handles
+ the retries from block/SCSI mid layer.
+
config FAULT_INJECTION_DEBUG_FS
bool "Debugfs entries for fault-injection capabilities"
depends on FAULT_INJECTION && SYSFS && DEBUG_FS