From f8474e0ec7ae72c9ac1b3bf2a36e9699f5637f34 Mon Sep 17 00:00:00 2001 From: Robin H. Johnson Date: Wed, 25 Apr 2007 00:07:52 -0700 Subject: [PATCH] libata: implement hotplug by polling This patch implements hotplug by polling - hp-poll. It is used for * hotplug event detection on controllers which don't provide PHY status changed interrupt. * hotplug event detection on disabled ports after reset failure. libata used to leave such ports in frozen state to protect the system from malfunctioning controller/device. With hp-poll, hotplug events no such ports are watched safely by polling, such that removing/replacing the malfunctioning device triggers EH retry on the port. There are three port ops for hp-poll - hp_poll_activate, hp_poll, hp_poll_deactivate. Only hp_poll is mandatory for hp-poll to work. This patch also implements SATA standard polling callbacks which poll SError.N/X bits - sata_std_hp_poll_activate() and sata_std_hp_poll(). By default, hp-poll is enabled only on disabled ports. If a LLD doesn't support hotplug interrupts but can poll for hotplug events, it should indicate so by setting ATA_FLAG_HP_POLLING which tells libata to turn on hp-poll by default. (This revision contains cleanups that prevent the original patch from working on newer kernels - robbat2). Signed-off-by: Tejun Heo Signed-off-by: Robin H. Johnson ----- The previous version contained a slight mis-merge. A prototype got placed inside the else tree of CONFIG_PM accidently. This revision just moves it to the correct place. --- drivers/ata/libata-core.c | 80 +++++++++++++++++++++++++++++- drivers/ata/libata-eh.c | 122 ++++++++++++++++++++++++++++++++++++++++++++- drivers/ata/libata.h | 2 + include/linux/libata.h | 11 ++++ 4 files changed, 212 insertions(+), 3 deletions(-) diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 6d0a946..68ea34b 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -3498,6 +3498,76 @@ void ata_std_postreset(struct ata_port *ap, unsigned int *classes) } /** + * sata_std_hp_poll_activate - standard SATA hotplug polling activation + * @ap: the target ata_port + * + * Activate SATA std hotplug polling. + * + * LOCKING: + * Kernel thread context (may sleep). + */ +void sata_std_hp_poll_activate(struct ata_port *ap) +{ + u32 serror; + + sata_scr_read(ap, SCR_ERROR, &serror); + serror &= SERR_PHYRDY_CHG | SERR_DEV_XCHG; + if (serror) + sata_scr_write(ap, SCR_ERROR, serror); + + ap->hp_poll_data = 0; +} + +/** + * sata_std_hp_poll - standard SATA hotplug polling callback + * @ap: the target ata_port + * + * Poll SError.N/X for hotplug event. Hotplug event is triggered + * only after PHY stays stable for at least one full polling + * interval after the first event detection. + * + * LOCKING: + * spin_lock_irqsave(host_set lock) + * + * RETURNS: + * 1 if hotplug event has occurred, 0 otherwise. + */ +int sata_std_hp_poll(struct ata_port *ap) +{ + unsigned long state = (unsigned long)ap->hp_poll_data; + u32 serror; + int rc = 0; + + sata_scr_read(ap, SCR_ERROR, &serror); + serror &= SERR_PHYRDY_CHG | SERR_DEV_XCHG; + + switch (state) { + case 0: + if (serror) { + /* PHY status could be bouncing crazy due to + * faulty controller or device. Don't fire + * hotplug event till hotplug event stays + * quiescent for one full polling interval. + */ + sata_scr_write(ap, SCR_ERROR, serror); + state = 1; + } + break; + + case 1: + if (!serror) + rc = 1; + else + sata_scr_write(ap, SCR_ERROR, serror); + break; + } + + ap->hp_poll_data = (void *)state; + + return rc; +} + +/** * ata_dev_same_device - Determine whether new ID matches configured device * @dev: device to compare against * @new_class: class of the new device @@ -6160,6 +6230,7 @@ void ata_host_init(struct ata_host *host, struct device *dev, host->dev = dev; host->flags = flags; host->ops = ops; + INIT_DELAYED_WORK(&host->hp_poll_task, ata_hp_poll_worker); } /** @@ -6387,11 +6458,15 @@ void ata_port_detach(struct ata_port *ap) ata_port_wait_eh(ap); - /* Flush hotplug task. The sequence is similar to + /* deactivate hotplug polling */ + ata_hp_poll_deactivate(ap); + + /* Flush hotplug tasks. The sequence is similar to * ata_port_flush_task(). */ flush_workqueue(ata_aux_wq); cancel_delayed_work(&ap->hotplug_task); + cancel_delayed_work(&ap->host->hp_poll_task); flush_workqueue(ata_aux_wq); skip_eh: @@ -6752,6 +6827,9 @@ EXPORT_SYMBOL_GPL(ata_std_softreset); EXPORT_SYMBOL_GPL(sata_port_hardreset); EXPORT_SYMBOL_GPL(sata_std_hardreset); EXPORT_SYMBOL_GPL(ata_std_postreset); +EXPORT_SYMBOL_GPL(sata_std_hp_poll_activate); +EXPORT_SYMBOL_GPL(sata_std_hp_poll); +EXPORT_SYMBOL_GPL(ata_dev_revalidate); EXPORT_SYMBOL_GPL(ata_dev_classify); EXPORT_SYMBOL_GPL(ata_dev_pair); EXPORT_SYMBOL_GPL(ata_port_disable); diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 0dbee55..c2bd0d5 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -33,6 +33,7 @@ */ #include +#include #include #include #include @@ -49,9 +50,14 @@ enum { ATA_EH_SPDN_SPEED_DOWN = (1 << 1), ATA_EH_SPDN_FALLBACK_TO_PIO = (1 << 2), }; +static unsigned long hotplug_polling_interval = 2000; +module_param(hotplug_polling_interval, ulong, 0644); +MODULE_PARM_DESC(hotplug_polling_interval, + "Hotplug polling interval in milliseconds (default 2000)"); static void __ata_port_freeze(struct ata_port *ap); static void ata_eh_finish(struct ata_port *ap); +static void ata_hp_poll_activate(struct ata_port *ap); #ifdef CONFIG_PM static void ata_eh_handle_port_suspend(struct ata_port *ap); static void ata_eh_handle_port_resume(struct ata_port *ap); @@ -644,7 +650,10 @@ int ata_port_freeze(struct ata_port *ap) * ata_eh_freeze_port - EH helper to freeze port * @ap: ATA port to freeze * - * Freeze @ap. + * Freeze @ap. As the 'freeze' operation means 'shutdown event + * reporting', it is a perfect place to deactivate hp-poll. Note + * that ata_port_freeze() always invokes EH and eventually this + * function, so deactivating hp-poll in this function is enough. * * LOCKING: * None. @@ -656,6 +665,8 @@ void ata_eh_freeze_port(struct ata_port *ap) if (!ap->ops->error_handler) return; + ata_hp_poll_deactivate(ap); + spin_lock_irqsave(ap->lock, flags); __ata_port_freeze(ap); spin_unlock_irqrestore(ap->lock, flags); @@ -665,7 +676,7 @@ void ata_eh_freeze_port(struct ata_port *ap) * ata_port_thaw_port - EH helper to thaw port * @ap: ATA port to thaw * - * Thaw frozen port @ap. + * Thaw frozen port @ap and activate hp-poll if necessary. * * LOCKING: * None. @@ -686,6 +697,9 @@ void ata_eh_thaw_port(struct ata_port *ap) spin_unlock_irqrestore(ap->lock, flags); + if (ap->flags & ATA_FLAG_HP_POLLING) + ata_hp_poll_activate(ap); + DPRINTK("ata%u port thawed\n", ap->print_id); } @@ -2229,6 +2243,9 @@ static int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, out: if (rc) { + /* recovery failed, activate hp-poll */ + ata_hp_poll_activate(ap); + for (i = 0; i < ATA_MAX_DEVICES; i++) ata_dev_disable(&ap->device[i]); } @@ -2419,3 +2436,104 @@ static void ata_eh_handle_port_resume(struct ata_port *ap) spin_unlock_irqrestore(ap->lock, flags); } #endif /* CONFIG_PM */ + +static unsigned long ata_hp_poll_delay(void) +{ + unsigned long interval, now; + + /* Group polls to polling interval boundaries to allow cpu to + * go idle as much as possible. + */ + interval = hotplug_polling_interval * HZ / 1000; + now = jiffies; + return roundup(now + 1, interval) - now; +} + +/** + * ata_hp_poll_activate - activate hotplug polling + * @ap: host port to activate hotplug polling for + * + * Activate hotplug probing for @ap. + * + * LOCKING: + * Kernel thread context (may sleep). + */ +static void ata_hp_poll_activate(struct ata_port *ap) +{ + unsigned long flags; + + if (!ap->ops->hp_poll || (ap->pflags & ATA_PFLAG_HP_POLL)) + return; + + if (ap->ops->hp_poll_activate) + ap->ops->hp_poll_activate(ap); + + queue_delayed_work(ata_aux_wq, &ap->host->hp_poll_task, + ata_hp_poll_delay()); + ap->hp_poll_data = 0; + + spin_lock_irqsave(ap->lock, flags); + ap->pflags |= ATA_PFLAG_HP_POLL; + spin_unlock_irqrestore(ap->lock, flags); +} + +/** + * ata_hp_poll_deactivate - deactivate hotplug polling + * @ap: host port to deactivate hotplug polling for + * + * Deactivate hotplug probing for @ap. + * + * LOCKING: + * Kernel thread context (may sleep). + */ +void ata_hp_poll_deactivate(struct ata_port *ap) +{ + unsigned long flags; + + if (!(ap->pflags & ATA_PFLAG_HP_POLL)) + return; + + spin_lock_irqsave(ap->lock, flags); + ap->pflags &= ~ATA_PFLAG_HP_POLL; + spin_unlock_irqrestore(ap->lock, flags); + + if (ap->ops->hp_poll_deactivate) + ap->ops->hp_poll_deactivate(ap); +} + +void ata_hp_poll_worker(struct work_struct *work) +{ + struct ata_host *host = + container_of(work, struct ata_host, hp_poll_task.work); + int i, nr_to_poll = 0; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + + for (i = 0; i < host->n_ports; i++) { + struct ata_port *ap = host->ports[i]; + int rc; + + if (!(ap->pflags & ATA_PFLAG_HP_POLL)) + continue; + + /* Poll. Positive return value indicates hotplug + * event while negative indicates error condition. + */ + rc = ap->ops->hp_poll(ap); + if (rc) { + if (rc > 0) { + ata_ehi_hotplugged(&ap->eh_info); + ata_port_freeze(ap); + } + ap->pflags &= ~ATA_PFLAG_HP_POLL; + } else + nr_to_poll++; + } + + spin_unlock_irqrestore(&host->lock, flags); + + if (nr_to_poll) + queue_delayed_work(ata_aux_wq, &host->hp_poll_task, + ata_hp_poll_delay()); +} diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 5f4d40c..52377dd 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -153,6 +153,8 @@ extern enum scsi_eh_timer_return ata_scsi_timed_out(struct scsi_cmnd *cmd); extern void ata_scsi_error(struct Scsi_Host *host); extern void ata_port_wait_eh(struct ata_port *ap); extern void ata_qc_schedule_eh(struct ata_queued_cmd *qc); +extern void ata_hp_poll_worker(struct work_struct *work); +extern void ata_hp_poll_deactivate(struct ata_port *ap); /* libata-sff.c */ extern u8 ata_irq_on(struct ata_port *ap); diff --git a/include/linux/libata.h b/include/linux/libata.h index 73b86dd..0fd2a8b 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -174,6 +174,7 @@ enum { ATA_FLAG_SETXFER_POLLING= (1 << 14), /* use polling for SETXFER */ ATA_FLAG_IGN_SIMPLEX = (1 << 15), /* ignore SIMPLEX */ ATA_FLAG_NO_IORDY = (1 << 16), /* controller lacks iordy */ + ATA_FLAG_HP_POLLING = (1 << 17), /* hotplug by polling */ /* The following flag belongs to ap->pflags but is kept in * ap->flags because it's referenced in many LLDs and will be @@ -191,6 +192,7 @@ enum { ATA_PFLAG_LOADING = (1 << 4), /* boot/loading probe */ ATA_PFLAG_UNLOADING = (1 << 5), /* module is unloading */ ATA_PFLAG_SCSI_HOTPLUG = (1 << 6), /* SCSI hotplug scheduled */ + ATA_PFLAG_HP_POLL = (1 << 7), /* hp-poll active */ ATA_PFLAG_FLUSH_PORT_TASK = (1 << 16), /* flush port task */ ATA_PFLAG_SUSPENDED = (1 << 17), /* port is suspended (power) */ @@ -379,6 +381,7 @@ struct ata_host { const struct ata_port_operations *ops; unsigned long flags; struct ata_port *simplex_claimed; /* channel owning the DMA */ + struct delayed_work hp_poll_task; struct ata_port *ports[0]; }; @@ -564,6 +567,7 @@ struct ata_port { pm_message_t pm_mesg; int *pm_result; + void *hp_poll_data; void *private_data; u8 sector_buf[ATA_SECT_SIZE]; /* owned by EH */ @@ -613,6 +617,10 @@ struct ata_port_operations { void (*error_handler) (struct ata_port *ap); void (*post_internal_cmd) (struct ata_queued_cmd *qc); + void (*hp_poll_activate) (struct ata_port *ap); + void (*hp_poll_deactivate) (struct ata_port *ap); + int (*hp_poll) (struct ata_port *ap); + irq_handler_t irq_handler; void (*irq_clear) (struct ata_port *); u8 (*irq_on) (struct ata_port *); @@ -694,6 +702,9 @@ extern int sata_port_hardreset(struct ata_port *ap, const unsigned long *timing); extern int sata_std_hardreset(struct ata_port *ap, unsigned int *class); extern void ata_std_postreset(struct ata_port *ap, unsigned int *classes); +extern void sata_std_hp_poll_activate(struct ata_port *ap); +extern int sata_std_hp_poll(struct ata_port *ap); +extern int ata_dev_revalidate(struct ata_device *dev, unsigned int flags); extern void ata_port_disable(struct ata_port *); extern void ata_std_ports(struct ata_ioports *ioaddr); #ifdef CONFIG_PCI -- 1.5.1.1