From 74d2eb8486d78767a6b0fa84d43cd85c83b43706 Mon Sep 17 00:00:00 2001 From: Preetham Chandru R Date: Thu, 8 Dec 2016 19:32:08 +0530 Subject: ata: ahci_tegra: Change to handle bad sectors Scanning the entire SATA disk for bad block takes a lot of time. Also there is an issue in tegra controller with bad block handling case so a WAR is merged to handle this issue. Since the driver gets to know the bad block sector in this WAR we can take advantage and update the filesystem about bad block info quickly. The kernel invokes a user space script with bad block sector which in turn updates the filesystem about bad sectors in the dri Bug 200156170 Change-Id: I70e64cac6356aea3d898d4fa6de62ad7131bd57a Signed-off-by: Preetham Chandru R Reviewed-on: http://git-master/r/1278669 (cherry picked from commit 3c0fdb862e3986e9535f8843aa0c5f99d17d9fa9) Reviewed-on: https://git-master.nvidia.com/r/1505549 GVS: Gerrit_Virtual_Submit Reviewed-by: Venu Byravarasu --- drivers/ata/tegra/ahci_tegra.c | 113 +++++++++++++++++++++++++++++++++++++++++ drivers/ata/tegra/ahci_tegra.h | 27 +++++++++- 2 files changed, 139 insertions(+), 1 deletion(-) (limited to 'drivers/ata') diff --git a/drivers/ata/tegra/ahci_tegra.c b/drivers/ata/tegra/ahci_tegra.c index d88b2c6b4..39ae34a71 100644 --- a/drivers/ata/tegra/ahci_tegra.c +++ b/drivers/ata/tegra/ahci_tegra.c @@ -313,6 +313,59 @@ static int tegra_ahci_softreset(struct ata_link *link, unsigned int *class, } +static int tegra_ahci_umh(unsigned long long block, char *block_dev) +{ + char *envp[] = { + "HOME=/", + "PATH=/sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin", + NULL }; + char *argv[TEGRA_BADBLK_MAX_ARGUMENTS + 1]; + char buf1[TEGRA_BADBLK_STRING_LENGTH] = { }; + char buf2[TEGRA_BADBLK_STRING_LENGTH] = { }; + int ret = 0; + + snprintf(buf1, sizeof(buf1), "%llu", block); + snprintf(buf2, sizeof(buf2), "%s", block_dev); + argv[TEGRA_BADBLK_COMMAND] = "/system/bin/badblk.sh"; + argv[TEGRA_BADBLK_COMMAND_PARAM1] = buf1; + argv[TEGRA_BADBLK_COMMAND_PARAM2] = buf2; + argv[TEGRA_BADBLK_MAX_ARGUMENTS] = NULL; + + ret = call_usermodehelper(argv[TEGRA_BADBLK_COMMAND], + argv, envp, UMH_WAIT_PROC); + if (ret == -ENOENT) { + argv[TEGRA_BADBLK_COMMAND] = "/home/ubuntu/badblk.sh"; + envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; + ret = call_usermodehelper(argv[TEGRA_BADBLK_COMMAND], + argv, envp, UMH_WAIT_PROC); + } + return ret; +} + +static void tegra_ahci_badblk_manage(struct work_struct *work) +{ + struct tegra_ahci_badblk_priv *badblk = + container_of(work, struct tegra_ahci_badblk_priv, badblk_work); + struct tegra_ahci_priv *tegra = + container_of(badblk, struct tegra_ahci_priv, badblk); + struct tegra_ahci_badblk_info *temp = NULL; + struct platform_device *pdev = tegra->pdev; + int ret = 0; + + spin_lock(&tegra->badblk.badblk_lock); + temp = tegra->badblk.head; + if (!temp) { + spin_unlock(&tegra->badblk.badblk_lock); + return; + } + tegra->badblk.head = temp->next; + + spin_unlock(&tegra->badblk.badblk_lock); + + ret = tegra_ahci_umh(temp->block, temp->block_dev); + devm_kfree(&pdev->dev, temp); +} + static void tegra_ahci_unbind(struct work_struct *work) { struct tegra_ahci_priv *tegra = @@ -326,8 +379,65 @@ static void tegra_ahci_unbind(struct work_struct *work) pdev->dev.driver->shutdown = NULL; } +static char *tegra_ahci_get_disk_name(struct scsi_cmnd *scsicmd) +{ + if (scsicmd && scsicmd->request && scsicmd->request->rq_disk) + return scsicmd->request->rq_disk->disk_name; + else + return NULL; +} + +static void tegra_ahci_schedule_badblk_work(struct ata_queued_cmd *qc, + struct ata_device *dev) +{ + + unsigned long long sector; + unsigned long long block; + char *disk_name = NULL; + struct tegra_ahci_badblk_info *temp = NULL; + struct ata_host *host = qc->ap->host; + struct ahci_host_priv *hpriv = host->private_data; + struct tegra_ahci_priv *tegra = hpriv->plat_data; + struct platform_device *pdev = tegra->pdev; + + disk_name = tegra_ahci_get_disk_name(qc->scsicmd); + sector = ata_tf_read_block(&qc->tf, dev); + block = (sector * 512) / 4096; + if (spin_trylock(&tegra->badblk.badblk_lock)) { + temp = devm_kzalloc(&pdev->dev, + sizeof(struct tegra_ahci_badblk_info), + GFP_ATOMIC); + if (temp) { + temp->block = block; + if (disk_name) + strncpy(temp->block_dev, disk_name, 99); + temp->next = tegra->badblk.head; + tegra->badblk.head = temp; + schedule_work(&tegra->badblk.badblk_work); + } + spin_unlock(&tegra->badblk.badblk_lock); + } +} + static void tegra_ahci_error_handler(struct ata_port *ap) { + int tag; + + for (tag = 0; tag < ATA_MAX_QUEUE; tag++) { + struct ata_queued_cmd *qc = __ata_qc_from_tag(ap, tag); + + if ((qc->flags & ATA_QCFLAG_FAILED) + && (qc->result_tf.feature & (ATA_UNC | ATA_AMNF))) { + struct ata_link *link; + struct ata_device *dev; + + ata_for_each_link(link, qc->ap, PMP_FIRST) + ata_for_each_dev(dev, link, ENABLED) + tegra_ahci_schedule_badblk_work(qc, + dev); + } + } + ahci_ops.error_handler(ap); if (!ata_dev_enabled(ap->link.device)) { if (!(ap->pflags & ATA_PFLAG_SUSPENDED)) { @@ -1430,6 +1540,9 @@ static int tegra_ahci_probe(struct platform_device *pdev) if (ret) goto poweroff_controller; + INIT_WORK(&tegra->badblk.badblk_work, tegra_ahci_badblk_manage); + tegra->badblk.head = NULL; + spin_lock_init(&tegra->badblk.badblk_lock); ret = ahci_platform_init_host(pdev, hpriv, &ahci_tegra_port_info, &ahci_platform_sht); diff --git a/drivers/ata/tegra/ahci_tegra.h b/drivers/ata/tegra/ahci_tegra.h index 49801a73f..eaf10bedd 100644 --- a/drivers/ata/tegra/ahci_tegra.h +++ b/drivers/ata/tegra/ahci_tegra.h @@ -1,7 +1,7 @@ /* * drivers/ata/ahci_tegra.h * - * Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2015-2017, NVIDIA CORPORATION. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -173,6 +173,16 @@ #define TEGRA_AHCI_READ_LOG_EXT_NOENTRY 0x80 +/* Badblock Management */ +#define TEGRA_BADBLK_STRING_LENGTH 100 + +enum tegra_badblk_arguments { + TEGRA_BADBLK_COMMAND, + TEGRA_BADBLK_COMMAND_PARAM1, + TEGRA_BADBLK_COMMAND_PARAM2, + TEGRA_BADBLK_MAX_ARGUMENTS, +}; + enum tegra_sata_bars { TEGRA_SATA_IPFS = 0, TEGRA_SATA_CONFIG, @@ -188,6 +198,18 @@ enum tegra_ahci_port_runtime_status { TEGRA_AHCI_PORT_RUNTIME_DEVSLP = 8, }; +struct tegra_ahci_badblk_info { + unsigned long long block; + char block_dev[TEGRA_BADBLK_STRING_LENGTH]; + struct tegra_ahci_badblk_info *next; +}; + +struct tegra_ahci_badblk_priv { + struct work_struct badblk_work; + struct tegra_ahci_badblk_info *head; + spinlock_t badblk_lock; +}; + struct tegra_ahci_priv { struct platform_device *pdev; void __iomem *base_list[TEGRA_SATA_BARS_MAX]; @@ -207,6 +229,7 @@ struct tegra_ahci_priv { struct pinctrl_state *devslp_pullup; struct tegra_prod *prod_list; struct work_struct work; + struct tegra_ahci_badblk_priv badblk; int devslp_gpio; bool devslp_override; bool devslp_pinmux_override; @@ -395,4 +418,6 @@ static inline void tegra_ahci_aux_update(struct ahci_host_priv *hpriv, u32 val, writel(rval, tegra->base_list[TEGRA_SATA_AUX] + offset); rval = readl(tegra->base_list[TEGRA_SATA_AUX] + offset); } + +u64 ata_tf_read_block(struct ata_taskfile *tf, struct ata_device *dev); #endif -- cgit v1.2.2