libata-eh.c
来自「linux 内核源代码」· C语言 代码 · 共 2,561 行 · 第 1/5 页
C
2,561 行
/* * libata-eh.c - libata error handling * * Maintained by: Jeff Garzik <jgarzik@pobox.com> * Please ALWAYS copy linux-ide@vger.kernel.org * on emails. * * Copyright 2006 Tejun Heo <htejun@gmail.com> * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * 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. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, * USA. * * * libata documentation is available via 'make {ps|pdf}docs', * as Documentation/DocBook/libata.* * * Hardware documentation available from http://www.t13.org/ and * http://www.sata-io.org/ * */#include <linux/kernel.h>#include <linux/pci.h>#include <scsi/scsi.h>#include <scsi/scsi_host.h>#include <scsi/scsi_eh.h>#include <scsi/scsi_device.h>#include <scsi/scsi_cmnd.h>#include "../scsi/scsi_transport_api.h"#include <linux/libata.h>#include "libata.h"enum { ATA_EH_SPDN_NCQ_OFF = (1 << 0), ATA_EH_SPDN_SPEED_DOWN = (1 << 1), ATA_EH_SPDN_FALLBACK_TO_PIO = (1 << 2),};/* Waiting in ->prereset can never be reliable. It's sometimes nice * to wait there but it can't be depended upon; otherwise, we wouldn't * be resetting. Just give it enough time for most drives to spin up. */enum { ATA_EH_PRERESET_TIMEOUT = 10 * HZ, ATA_EH_FASTDRAIN_INTERVAL = 3 * HZ,};/* The following table determines how we sequence resets. Each entry * represents timeout for that try. The first try can be soft or * hardreset. All others are hardreset if available. In most cases * the first reset w/ 10sec timeout should succeed. Following entries * are mostly for error handling, hotplug and retarded devices. */static const unsigned long ata_eh_reset_timeouts[] = { 10 * HZ, /* most drives spin up by 10sec */ 10 * HZ, /* > 99% working drives spin up before 20sec */ 35 * HZ, /* give > 30 secs of idleness for retarded devices */ 5 * HZ, /* and sweet one last chance */ /* > 1 min has elapsed, give up */};static void __ata_port_freeze(struct ata_port *ap);#ifdef CONFIG_PMstatic void ata_eh_handle_port_suspend(struct ata_port *ap);static void ata_eh_handle_port_resume(struct ata_port *ap);#else /* CONFIG_PM */static void ata_eh_handle_port_suspend(struct ata_port *ap){ }static void ata_eh_handle_port_resume(struct ata_port *ap){ }#endif /* CONFIG_PM */static void __ata_ehi_pushv_desc(struct ata_eh_info *ehi, const char *fmt, va_list args){ ehi->desc_len += vscnprintf(ehi->desc + ehi->desc_len, ATA_EH_DESC_LEN - ehi->desc_len, fmt, args);}/** * __ata_ehi_push_desc - push error description without adding separator * @ehi: target EHI * @fmt: printf format string * * Format string according to @fmt and append it to @ehi->desc. * * LOCKING: * spin_lock_irqsave(host lock) */void __ata_ehi_push_desc(struct ata_eh_info *ehi, const char *fmt, ...){ va_list args; va_start(args, fmt); __ata_ehi_pushv_desc(ehi, fmt, args); va_end(args);}/** * ata_ehi_push_desc - push error description with separator * @ehi: target EHI * @fmt: printf format string * * Format string according to @fmt and append it to @ehi->desc. * If @ehi->desc is not empty, ", " is added in-between. * * LOCKING: * spin_lock_irqsave(host lock) */void ata_ehi_push_desc(struct ata_eh_info *ehi, const char *fmt, ...){ va_list args; if (ehi->desc_len) __ata_ehi_push_desc(ehi, ", "); va_start(args, fmt); __ata_ehi_pushv_desc(ehi, fmt, args); va_end(args);}/** * ata_ehi_clear_desc - clean error description * @ehi: target EHI * * Clear @ehi->desc. * * LOCKING: * spin_lock_irqsave(host lock) */void ata_ehi_clear_desc(struct ata_eh_info *ehi){ ehi->desc[0] = '\0'; ehi->desc_len = 0;}/** * ata_port_desc - append port description * @ap: target ATA port * @fmt: printf format string * * Format string according to @fmt and append it to port * description. If port description is not empty, " " is added * in-between. This function is to be used while initializing * ata_host. The description is printed on host registration. * * LOCKING: * None. */void ata_port_desc(struct ata_port *ap, const char *fmt, ...){ va_list args; WARN_ON(!(ap->pflags & ATA_PFLAG_INITIALIZING)); if (ap->link.eh_info.desc_len) __ata_ehi_push_desc(&ap->link.eh_info, " "); va_start(args, fmt); __ata_ehi_pushv_desc(&ap->link.eh_info, fmt, args); va_end(args);}#ifdef CONFIG_PCI/** * ata_port_pbar_desc - append PCI BAR description * @ap: target ATA port * @bar: target PCI BAR * @offset: offset into PCI BAR * @name: name of the area * * If @offset is negative, this function formats a string which * contains the name, address, size and type of the BAR and * appends it to the port description. If @offset is zero or * positive, only name and offsetted address is appended. * * LOCKING: * None. */void ata_port_pbar_desc(struct ata_port *ap, int bar, ssize_t offset, const char *name){ struct pci_dev *pdev = to_pci_dev(ap->host->dev); char *type = ""; unsigned long long start, len; if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) type = "m"; else if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) type = "i"; start = (unsigned long long)pci_resource_start(pdev, bar); len = (unsigned long long)pci_resource_len(pdev, bar); if (offset < 0) ata_port_desc(ap, "%s %s%llu@0x%llx", name, type, len, start); else ata_port_desc(ap, "%s 0x%llx", name, start + offset);}#endif /* CONFIG_PCI */static void ata_ering_record(struct ata_ering *ering, int is_io, unsigned int err_mask){ struct ata_ering_entry *ent; WARN_ON(!err_mask); ering->cursor++; ering->cursor %= ATA_ERING_SIZE; ent = &ering->ring[ering->cursor]; ent->is_io = is_io; ent->err_mask = err_mask; ent->timestamp = get_jiffies_64();}static void ata_ering_clear(struct ata_ering *ering){ memset(ering, 0, sizeof(*ering));}static int ata_ering_map(struct ata_ering *ering, int (*map_fn)(struct ata_ering_entry *, void *), void *arg){ int idx, rc = 0; struct ata_ering_entry *ent; idx = ering->cursor; do { ent = &ering->ring[idx]; if (!ent->err_mask) break; rc = map_fn(ent, arg); if (rc) break; idx = (idx - 1 + ATA_ERING_SIZE) % ATA_ERING_SIZE; } while (idx != ering->cursor); return rc;}static unsigned int ata_eh_dev_action(struct ata_device *dev){ struct ata_eh_context *ehc = &dev->link->eh_context; return ehc->i.action | ehc->i.dev_action[dev->devno];}static void ata_eh_clear_action(struct ata_link *link, struct ata_device *dev, struct ata_eh_info *ehi, unsigned int action){ struct ata_device *tdev; if (!dev) { ehi->action &= ~action; ata_link_for_each_dev(tdev, link) ehi->dev_action[tdev->devno] &= ~action; } else { /* doesn't make sense for port-wide EH actions */ WARN_ON(!(action & ATA_EH_PERDEV_MASK)); /* break ehi->action into ehi->dev_action */ if (ehi->action & action) { ata_link_for_each_dev(tdev, link) ehi->dev_action[tdev->devno] |= ehi->action & action; ehi->action &= ~action; } /* turn off the specified per-dev action */ ehi->dev_action[dev->devno] &= ~action; }}/** * ata_scsi_timed_out - SCSI layer time out callback * @cmd: timed out SCSI command * * Handles SCSI layer timeout. We race with normal completion of * the qc for @cmd. If the qc is already gone, we lose and let * the scsi command finish (EH_HANDLED). Otherwise, the qc has * timed out and EH should be invoked. Prevent ata_qc_complete() * from finishing it by setting EH_SCHEDULED and return * EH_NOT_HANDLED. * * TODO: kill this function once old EH is gone. * * LOCKING: * Called from timer context * * RETURNS: * EH_HANDLED or EH_NOT_HANDLED */enum scsi_eh_timer_return ata_scsi_timed_out(struct scsi_cmnd *cmd){ struct Scsi_Host *host = cmd->device->host; struct ata_port *ap = ata_shost_to_port(host); unsigned long flags; struct ata_queued_cmd *qc; enum scsi_eh_timer_return ret; DPRINTK("ENTER\n"); if (ap->ops->error_handler) { ret = EH_NOT_HANDLED; goto out; } ret = EH_HANDLED; spin_lock_irqsave(ap->lock, flags); qc = ata_qc_from_tag(ap, ap->link.active_tag); if (qc) { WARN_ON(qc->scsicmd != cmd); qc->flags |= ATA_QCFLAG_EH_SCHEDULED; qc->err_mask |= AC_ERR_TIMEOUT; ret = EH_NOT_HANDLED; } spin_unlock_irqrestore(ap->lock, flags); out: DPRINTK("EXIT, ret=%d\n", ret); return ret;}/** * ata_scsi_error - SCSI layer error handler callback * @host: SCSI host on which error occurred * * Handles SCSI-layer-thrown error events. * * LOCKING: * Inherited from SCSI layer (none, can sleep) * * RETURNS: * Zero. */void ata_scsi_error(struct Scsi_Host *host){ struct ata_port *ap = ata_shost_to_port(host); int i; unsigned long flags; DPRINTK("ENTER\n"); /* synchronize with port task */ ata_port_flush_task(ap); /* synchronize with host lock and sort out timeouts */ /* For new EH, all qcs are finished in one of three ways - * normal completion, error completion, and SCSI timeout. * Both cmpletions can race against SCSI timeout. When normal * completion wins, the qc never reaches EH. When error * completion wins, the qc has ATA_QCFLAG_FAILED set. * * When SCSI timeout wins, things are a bit more complex. * Normal or error completion can occur after the timeout but * before this point. In such cases, both types of * completions are honored. A scmd is determined to have * timed out iff its associated qc is active and not failed. */ if (ap->ops->error_handler) { struct scsi_cmnd *scmd, *tmp; int nr_timedout = 0; spin_lock_irqsave(ap->lock, flags); list_for_each_entry_safe(scmd, tmp, &host->eh_cmd_q, eh_entry) { struct ata_queued_cmd *qc; for (i = 0; i < ATA_MAX_QUEUE; i++) { qc = __ata_qc_from_tag(ap, i); if (qc->flags & ATA_QCFLAG_ACTIVE && qc->scsicmd == scmd) break; } if (i < ATA_MAX_QUEUE) { /* the scmd has an associated qc */ if (!(qc->flags & ATA_QCFLAG_FAILED)) { /* which hasn't failed yet, timeout */ qc->err_mask |= AC_ERR_TIMEOUT; qc->flags |= ATA_QCFLAG_FAILED; nr_timedout++; } } else { /* Normal completion occurred after * SCSI timeout but before this point. * Successfully complete it. */ scmd->retries = scmd->allowed; scsi_eh_finish_cmd(scmd, &ap->eh_done_q); } } /* If we have timed out qcs. They belong to EH from * this point but the state of the controller is * unknown. Freeze the port to make sure the IRQ * handler doesn't diddle with those qcs. This must * be done atomically w.r.t. setting QCFLAG_FAILED. */ if (nr_timedout) __ata_port_freeze(ap); spin_unlock_irqrestore(ap->lock, flags); /* initialize eh_tries */ ap->eh_tries = ATA_EH_MAX_TRIES; } else spin_unlock_wait(ap->lock); repeat: /* invoke error handler */ if (ap->ops->error_handler) { struct ata_link *link; /* kill fast drain timer */ del_timer_sync(&ap->fastdrain_timer); /* process port resume request */ ata_eh_handle_port_resume(ap); /* fetch & clear EH info */ spin_lock_irqsave(ap->lock, flags); __ata_port_for_each_link(link, ap) { memset(&link->eh_context, 0, sizeof(link->eh_context)); link->eh_context.i = link->eh_info; memset(&link->eh_info, 0, sizeof(link->eh_info)); } ap->pflags |= ATA_PFLAG_EH_IN_PROGRESS; ap->pflags &= ~ATA_PFLAG_EH_PENDING; ap->excl_link = NULL; /* don't maintain exclusion over EH */ spin_unlock_irqrestore(ap->lock, flags); /* invoke EH, skip if unloading or suspended */ if (!(ap->pflags & (ATA_PFLAG_UNLOADING | ATA_PFLAG_SUSPENDED))) ap->ops->error_handler(ap); else ata_eh_finish(ap); /* process port suspend request */ ata_eh_handle_port_suspend(ap); /* Exception might have happend after ->error_handler * recovered the port but before this point. Repeat * EH in such case. */ spin_lock_irqsave(ap->lock, flags); if (ap->pflags & ATA_PFLAG_EH_PENDING) { if (--ap->eh_tries) { spin_unlock_irqrestore(ap->lock, flags); goto repeat; } ata_port_printk(ap, KERN_ERR, "EH pending after %d " "tries, giving up\n", ATA_EH_MAX_TRIES); ap->pflags &= ~ATA_PFLAG_EH_PENDING; } /* this run is complete, make sure EH info is clear */ __ata_port_for_each_link(link, ap) memset(&link->eh_info, 0, sizeof(link->eh_info)); /* Clear host_eh_scheduled while holding ap->lock such * that if exception occurs after this point but * before EH completion, SCSI midlayer will * re-initiate EH. */ host->host_eh_scheduled = 0; spin_unlock_irqrestore(ap->lock, flags); } else { WARN_ON(ata_qc_from_tag(ap, ap->link.active_tag) == NULL); ap->ops->eng_timeout(ap); } /* finish or retry handled scmd's and clean up */ WARN_ON(host->host_failed || !list_empty(&host->eh_cmd_q)); scsi_eh_flush_done_q(&ap->eh_done_q); /* clean up */ spin_lock_irqsave(ap->lock, flags); if (ap->pflags & ATA_PFLAG_LOADING) ap->pflags &= ~ATA_PFLAG_LOADING; else if (ap->pflags & ATA_PFLAG_SCSI_HOTPLUG) queue_delayed_work(ata_aux_wq, &ap->hotplug_task, 0);
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?