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 + -
显示快捷键?