From ad72cf9885c536e3adae03f8337557ac9dd1e4bb Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Fri, 2 Jul 2010 10:03:52 +0200 Subject: libata: take advantage of cmwq and remove concurrency limitations libata has two concurrency related limitations. a. ata_wq which is used for polling PIO has single thread per CPU. If there are multiple devices doing polling PIO on the same CPU, they can't be executed simultaneously. b. ata_aux_wq which is used for SCSI probing has single thread. In cases where SCSI probing is stalled for extended period of time which is possible for ATAPI devices, this will stall all probing. #a is solved by increasing maximum concurrency of ata_wq. Please note that polling PIO might be used under allocation path and thus needs to be served by a separate wq with a rescuer. #b is solved by using the default wq instead and achieving exclusion via per-port mutex. Signed-off-by: Tejun Heo Acked-by: Jeff Garzik --- include/linux/libata.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/linux/libata.h') diff --git a/include/linux/libata.h b/include/linux/libata.h index b85f3ff34d7d..f010f18a0f86 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -751,6 +751,7 @@ struct ata_port { struct ata_host *host; struct device *dev; + struct mutex scsi_scan_mutex; struct delayed_work hotplug_task; struct work_struct scsi_rescan_task; -- cgit v1.2.3 From e2f3d75fc0e4a0d03c61872bad39ffa2e74a04ff Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Tue, 7 Sep 2010 14:05:31 +0200 Subject: libata: skip EH autopsy and recovery during suspend For some mysterious reason, certain hardware reacts badly to usual EH actions while the system is going for suspend. As the devices won't be needed until the system is resumed, ask EH to skip usual autopsy and recovery and proceed directly to suspend. Signed-off-by: Tejun Heo Tested-by: Stephan Diestelhorst Cc: stable@kernel.org Signed-off-by: Jeff Garzik --- drivers/ata/libata-core.c | 14 +++++++++++++- drivers/ata/libata-eh.c | 4 ++++ include/linux/libata.h | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) (limited to 'include/linux/libata.h') diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index c035b3d041ee..932eaee50245 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -5418,6 +5418,7 @@ static int ata_host_request_pm(struct ata_host *host, pm_message_t mesg, */ int ata_host_suspend(struct ata_host *host, pm_message_t mesg) { + unsigned int ehi_flags = ATA_EHI_QUIET; int rc; /* @@ -5426,7 +5427,18 @@ int ata_host_suspend(struct ata_host *host, pm_message_t mesg) */ ata_lpm_enable(host); - rc = ata_host_request_pm(host, mesg, 0, ATA_EHI_QUIET, 1); + /* + * On some hardware, device fails to respond after spun down + * for suspend. As the device won't be used before being + * resumed, we don't need to touch the device. Ask EH to skip + * the usual stuff and proceed directly to suspend. + * + * http://thread.gmane.org/gmane.linux.ide/46764 + */ + if (mesg.event == PM_EVENT_SUSPEND) + ehi_flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_NO_RECOVERY; + + rc = ata_host_request_pm(host, mesg, 0, ehi_flags, 1); if (rc == 0) host->dev->power.power_state = mesg; return rc; diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index c9ae299b8342..e48302eae55f 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -3235,6 +3235,10 @@ static int ata_eh_skip_recovery(struct ata_link *link) if (link->flags & ATA_LFLAG_DISABLED) return 1; + /* skip if explicitly requested */ + if (ehc->i.flags & ATA_EHI_NO_RECOVERY) + return 1; + /* thaw frozen port and recover failed devices */ if ((ap->pflags & ATA_PFLAG_FROZEN) || ata_link_nr_enabled(link)) return 0; diff --git a/include/linux/libata.h b/include/linux/libata.h index f010f18a0f86..7de282d8bedf 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -335,6 +335,7 @@ enum { ATA_EHI_HOTPLUGGED = (1 << 0), /* could have been hotplugged */ ATA_EHI_NO_AUTOPSY = (1 << 2), /* no autopsy */ ATA_EHI_QUIET = (1 << 3), /* be quiet */ + ATA_EHI_NO_RECOVERY = (1 << 4), /* no recovery */ ATA_EHI_DID_SOFTRESET = (1 << 16), /* already soft-reset this port */ ATA_EHI_DID_HARDRESET = (1 << 17), /* already soft-reset this port */ -- cgit v1.2.3 From ea3c64506ea7965f86f030155e6fdef381de10e2 Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Tue, 31 Aug 2010 16:20:36 -0700 Subject: libata-sff: Reenable Port Multiplier after libata-sff remodeling. Keep track of the link on the which the current request is in progress. It allows support of links behind port multiplier. Not all libata-sff is PMP compliant. Code for native BMDMA controller does not take in accound PMP. Tested on Marvell 7042 and Sil7526. Signed-off-by: Gwendal Grignou Signed-off-by: Jeff Garzik --- drivers/ata/libata-sff.c | 38 ++++++++++++++++++++++++++++---------- drivers/ata/sata_mv.c | 2 +- include/linux/libata.h | 3 ++- 3 files changed, 31 insertions(+), 12 deletions(-) (limited to 'include/linux/libata.h') diff --git a/drivers/ata/libata-sff.c b/drivers/ata/libata-sff.c index dee3c2c52562..e30c537cce32 100644 --- a/drivers/ata/libata-sff.c +++ b/drivers/ata/libata-sff.c @@ -1045,7 +1045,8 @@ static void ata_hsm_qc_complete(struct ata_queued_cmd *qc, int in_wq) int ata_sff_hsm_move(struct ata_port *ap, struct ata_queued_cmd *qc, u8 status, int in_wq) { - struct ata_eh_info *ehi = &ap->link.eh_info; + struct ata_link *link = qc->dev->link; + struct ata_eh_info *ehi = &link->eh_info; unsigned long flags = 0; int poll_next; @@ -1301,8 +1302,14 @@ fsm_start: } EXPORT_SYMBOL_GPL(ata_sff_hsm_move); -void ata_sff_queue_pio_task(struct ata_port *ap, unsigned long delay) +void ata_sff_queue_pio_task(struct ata_link *link, unsigned long delay) { + struct ata_port *ap = link->ap; + + WARN_ON((ap->sff_pio_task_link != NULL) && + (ap->sff_pio_task_link != link)); + ap->sff_pio_task_link = link; + /* may fail if ata_sff_flush_pio_task() in progress */ queue_delayed_work(ata_sff_wq, &ap->sff_pio_task, msecs_to_jiffies(delay)); @@ -1324,14 +1331,18 @@ static void ata_sff_pio_task(struct work_struct *work) { struct ata_port *ap = container_of(work, struct ata_port, sff_pio_task.work); + struct ata_link *link = ap->sff_pio_task_link; struct ata_queued_cmd *qc; u8 status; int poll_next; + BUG_ON(ap->sff_pio_task_link == NULL); /* qc can be NULL if timeout occurred */ - qc = ata_qc_from_tag(ap, ap->link.active_tag); - if (!qc) + qc = ata_qc_from_tag(ap, link->active_tag); + if (!qc) { + ap->sff_pio_task_link = NULL; return; + } fsm_start: WARN_ON_ONCE(ap->hsm_task_state == HSM_ST_IDLE); @@ -1348,11 +1359,16 @@ fsm_start: msleep(2); status = ata_sff_busy_wait(ap, ATA_BUSY, 10); if (status & ATA_BUSY) { - ata_sff_queue_pio_task(ap, ATA_SHORT_PAUSE); + ata_sff_queue_pio_task(link, ATA_SHORT_PAUSE); return; } } + /* + * hsm_move() may trigger another command to be processed. + * clean the link beforehand. + */ + ap->sff_pio_task_link = NULL; /* move the HSM */ poll_next = ata_sff_hsm_move(ap, qc, status, 1); @@ -1379,6 +1395,7 @@ fsm_start: unsigned int ata_sff_qc_issue(struct ata_queued_cmd *qc) { struct ata_port *ap = qc->ap; + struct ata_link *link = qc->dev->link; /* Use polling pio if the LLD doesn't handle * interrupt driven pio and atapi CDB interrupt. @@ -1399,7 +1416,7 @@ unsigned int ata_sff_qc_issue(struct ata_queued_cmd *qc) ap->hsm_task_state = HSM_ST_LAST; if (qc->tf.flags & ATA_TFLAG_POLLING) - ata_sff_queue_pio_task(ap, 0); + ata_sff_queue_pio_task(link, 0); break; @@ -1412,7 +1429,7 @@ unsigned int ata_sff_qc_issue(struct ata_queued_cmd *qc) if (qc->tf.flags & ATA_TFLAG_WRITE) { /* PIO data out protocol */ ap->hsm_task_state = HSM_ST_FIRST; - ata_sff_queue_pio_task(ap, 0); + ata_sff_queue_pio_task(link, 0); /* always send first data block using the * ata_sff_pio_task() codepath. @@ -1422,7 +1439,7 @@ unsigned int ata_sff_qc_issue(struct ata_queued_cmd *qc) ap->hsm_task_state = HSM_ST; if (qc->tf.flags & ATA_TFLAG_POLLING) - ata_sff_queue_pio_task(ap, 0); + ata_sff_queue_pio_task(link, 0); /* if polling, ata_sff_pio_task() handles the * rest. otherwise, interrupt handler takes @@ -1444,7 +1461,7 @@ unsigned int ata_sff_qc_issue(struct ata_queued_cmd *qc) /* send cdb by polling if no cdb interrupt */ if ((!(qc->dev->flags & ATA_DFLAG_CDB_INTR)) || (qc->tf.flags & ATA_TFLAG_POLLING)) - ata_sff_queue_pio_task(ap, 0); + ata_sff_queue_pio_task(link, 0); break; default: @@ -2737,6 +2754,7 @@ EXPORT_SYMBOL_GPL(ata_bmdma_dumb_qc_prep); unsigned int ata_bmdma_qc_issue(struct ata_queued_cmd *qc) { struct ata_port *ap = qc->ap; + struct ata_link *link = qc->dev->link; /* defer PIO handling to sff_qc_issue */ if (!ata_is_dma(qc->tf.protocol)) @@ -2765,7 +2783,7 @@ unsigned int ata_bmdma_qc_issue(struct ata_queued_cmd *qc) /* send cdb by polling if no cdb interrupt */ if (!(qc->dev->flags & ATA_DFLAG_CDB_INTR)) - ata_sff_queue_pio_task(ap, 0); + ata_sff_queue_pio_task(link, 0); break; default: diff --git a/drivers/ata/sata_mv.c b/drivers/ata/sata_mv.c index 81982594a014..a9fd9709c262 100644 --- a/drivers/ata/sata_mv.c +++ b/drivers/ata/sata_mv.c @@ -2284,7 +2284,7 @@ static unsigned int mv_qc_issue_fis(struct ata_queued_cmd *qc) } if (qc->tf.flags & ATA_TFLAG_POLLING) - ata_sff_queue_pio_task(ap, 0); + ata_sff_queue_pio_task(link, 0); return 0; } diff --git a/include/linux/libata.h b/include/linux/libata.h index 7de282d8bedf..45fb2967b66d 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -724,6 +724,7 @@ struct ata_port { struct ata_ioports ioaddr; /* ATA cmd/ctl/dma register blocks */ u8 ctl; /* cache of ATA control register */ u8 last_ctl; /* Cache last written value */ + struct ata_link* sff_pio_task_link; /* link currently used */ struct delayed_work sff_pio_task; #ifdef CONFIG_ATA_BMDMA struct ata_bmdma_prd *bmdma_prd; /* BMDMA SG list */ @@ -1595,7 +1596,7 @@ extern void ata_sff_irq_on(struct ata_port *ap); extern void ata_sff_irq_clear(struct ata_port *ap); extern int ata_sff_hsm_move(struct ata_port *ap, struct ata_queued_cmd *qc, u8 status, int in_wq); -extern void ata_sff_queue_pio_task(struct ata_port *ap, unsigned long delay); +extern void ata_sff_queue_pio_task(struct ata_link *link, unsigned long delay); extern unsigned int ata_sff_qc_issue(struct ata_queued_cmd *qc); extern bool ata_sff_qc_fill_rtf(struct ata_queued_cmd *qc); extern unsigned int ata_sff_port_intr(struct ata_port *ap, -- cgit v1.2.3 From d9027470b88631d0956ac37cdadfdeb9cdcf2c99 Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Tue, 25 May 2010 12:31:38 -0700 Subject: [libata] Add ATA transport class This is a scheleton for libata transport class. All information is read only, exporting information from libata: - ata_port class: one per ATA port - ata_link class: one per ATA port or 15 for SATA Port Multiplier - ata_device class: up to 2 for PATA link, usually one for SATA. Signed-off-by: Gwendal Grignou Reviewed-by: Grant Grundler Signed-off-by: Jeff Garzik --- Documentation/ABI/testing/sysfs-ata | 99 +++++ drivers/ata/Makefile | 2 +- drivers/ata/libata-core.c | 49 ++- drivers/ata/libata-eh.c | 35 +- drivers/ata/libata-pmp.c | 18 +- drivers/ata/libata-scsi.c | 21 +- drivers/ata/libata-transport.c | 773 ++++++++++++++++++++++++++++++++++++ drivers/ata/libata-transport.h | 18 + drivers/ata/libata.h | 7 + include/linux/libata.h | 5 + 10 files changed, 987 insertions(+), 40 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-ata create mode 100644 drivers/ata/libata-transport.c create mode 100644 drivers/ata/libata-transport.h (limited to 'include/linux/libata.h') diff --git a/Documentation/ABI/testing/sysfs-ata b/Documentation/ABI/testing/sysfs-ata new file mode 100644 index 000000000000..0a932155cbba --- /dev/null +++ b/Documentation/ABI/testing/sysfs-ata @@ -0,0 +1,99 @@ +What: /sys/class/ata_... +Date: August 2008 +Contact: Gwendal Grignou +Description: + +Provide a place in sysfs for storing the ATA topology of the system. This allows +retrieving various information about ATA objects. + +Files under /sys/class/ata_port +------------------------------- + + For each port, a directory ataX is created where X is the ata_port_id of + the port. The device parent is the ata host device. + +idle_irq (read) + + Number of IRQ received by the port while idle [some ata HBA only]. + +nr_pmp_links (read) + + If a SATA Port Multiplier (PM) is connected, number of link behind it. + +Files under /sys/class/ata_link +------------------------------- + + Behind each port, there is a ata_link. If there is a SATA PM in the + topology, 15 ata_link objects are created. + + If a link is behind a port, the directory name is linkX, where X is + ata_port_id of the port. + If a link is behind a PM, its name is linkX.Y where X is ata_port_id + of the parent port and Y the PM port. + +hw_sata_spd_limit + + Maximum speed supported by the connected SATA device. + +sata_spd_limit + + Maximum speed imposed by libata. + +sata_spd + + Current speed of the link [1.5, 3Gps,...]. + +Files under /sys/class/ata_device +--------------------------------- + + Behind each link, up to two ata device are created. + The name of the directory is devX[.Y].Z where: + - X is ata_port_id of the port where the device is connected, + - Y the port of the PM if any, and + - Z the device id: for PATA, there is usually 2 devices [0,1], + only 1 for SATA. + +class + Device class. Can be "ata" for disk, "atapi" for packet device, + "pmp" for PM, or "none" if no device was found behind the link. + +dma_mode + + Transfer modes supported by the device when in DMA mode. + Mostly used by PATA device. + +pio_mode + + Transfer modes supported by the device when in PIO mode. + Mostly used by PATA device. + +xfer_mode + + Current transfer mode. + +id + + Cached result of IDENTIFY command, as described in ATA8 7.16 and 7.17. + Only valid if the device is not a PM. + +gscr + + Cached result of the dump of PM GSCR register. + Valid registers are: + 0: SATA_PMP_GSCR_PROD_ID, + 1: SATA_PMP_GSCR_REV, + 2: SATA_PMP_GSCR_PORT_INFO, + 32: SATA_PMP_GSCR_ERROR, + 33: SATA_PMP_GSCR_ERROR_EN, + 64: SATA_PMP_GSCR_FEAT, + 96: SATA_PMP_GSCR_FEAT_EN, + 130: SATA_PMP_GSCR_SII_GPIO + Only valid if the device is a PM. + +spdn_cnt + + Number of time libata decided to lower the speed of link due to errors. + +ering + + Formatted output of the error ring of the device. diff --git a/drivers/ata/Makefile b/drivers/ata/Makefile index d5df04a395ca..ccd461bb5ee4 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -99,7 +99,7 @@ obj-$(CONFIG_ATA_GENERIC) += ata_generic.o # Should be last libata driver obj-$(CONFIG_PATA_LEGACY) += pata_legacy.o -libata-objs := libata-core.o libata-scsi.o libata-eh.o +libata-objs := libata-core.o libata-scsi.o libata-eh.o libata-transport.o libata-$(CONFIG_ATA_SFF) += libata-sff.o libata-$(CONFIG_SATA_PMP) += libata-pmp.o libata-$(CONFIG_ATA_ACPI) += libata-acpi.o diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 932eaee50245..4012b33e8b8a 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -68,7 +68,7 @@ #include #include "libata.h" - +#include "libata-transport.h" /* debounce timing parameters in msecs { interval, duration, timeout } */ const unsigned long sata_deb_timing_normal[] = { 5, 100, 2000 }; @@ -1017,7 +1017,7 @@ const char *ata_mode_string(unsigned long xfer_mask) return ""; } -static const char *sata_spd_string(unsigned int spd) +const char *sata_spd_string(unsigned int spd) { static const char * const spd_str[] = { "1.5 Gbps", @@ -5517,7 +5517,8 @@ void ata_link_init(struct ata_port *ap, struct ata_link *link, int pmp) int i; /* clear everything except for devices */ - memset(link, 0, offsetof(struct ata_link, device[0])); + memset((void *)link + ATA_LINK_CLEAR_BEGIN, 0, + ATA_LINK_CLEAR_END - ATA_LINK_CLEAR_BEGIN); link->ap = ap; link->pmp = pmp; @@ -5591,7 +5592,7 @@ struct ata_port *ata_port_alloc(struct ata_host *host) ap = kzalloc(sizeof(*ap), GFP_KERNEL); if (!ap) return NULL; - + ap->pflags |= ATA_PFLAG_INITIALIZING; ap->lock = &host->lock; ap->print_id = -1; @@ -6093,9 +6094,18 @@ int ata_host_register(struct ata_host *host, struct scsi_host_template *sht) for (i = 0; i < host->n_ports; i++) host->ports[i]->print_id = ata_print_id++; + + /* Create associated sysfs transport objects */ + for (i = 0; i < host->n_ports; i++) { + rc = ata_tport_add(host->dev,host->ports[i]); + if (rc) { + goto err_tadd; + } + } + rc = ata_scsi_add_hosts(host, sht); if (rc) - return rc; + goto err_tadd; /* associate with ACPI nodes */ ata_acpi_associate(host); @@ -6136,6 +6146,13 @@ int ata_host_register(struct ata_host *host, struct scsi_host_template *sht) } return 0; + + err_tadd: + while (--i >= 0) { + ata_tport_delete(host->ports[i]); + } + return rc; + } /** @@ -6226,6 +6243,13 @@ static void ata_port_detach(struct ata_port *ap) cancel_rearming_delayed_work(&ap->hotplug_task); skip_eh: + if (ap->pmp_link) { + int i; + for (i = 0; i < SATA_PMP_MAX_PORTS; i++) + ata_tlink_delete(&ap->pmp_link[i]); + } + ata_tport_delete(ap); + /* remove the associated SCSI host */ scsi_remove_host(ap->scsi_host); } @@ -6542,7 +6566,7 @@ static void __init ata_parse_force_param(void) static int __init ata_init(void) { - int rc = -ENOMEM; + int rc; ata_parse_force_param(); @@ -6552,12 +6576,25 @@ static int __init ata_init(void) return rc; } + libata_transport_init(); + ata_scsi_transport_template = ata_attach_transport(); + if (!ata_scsi_transport_template) { + ata_sff_exit(); + rc = -ENOMEM; + goto err_out; + } + printk(KERN_DEBUG "libata version " DRV_VERSION " loaded.\n"); return 0; + +err_out: + return rc; } static void __exit ata_exit(void) { + ata_release_transport(ata_scsi_transport_template); + libata_transport_exit(); ata_sff_exit(); kfree(ata_force_tbl); } diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index e48302eae55f..95838b382231 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -57,6 +57,7 @@ enum { /* error flags */ ATA_EFLAG_IS_IO = (1 << 0), ATA_EFLAG_DUBIOUS_XFER = (1 << 1), + ATA_EFLAG_OLD_ER = (1 << 31), /* error categories */ ATA_ECAT_NONE = 0, @@ -396,14 +397,9 @@ static struct ata_ering_entry *ata_ering_top(struct ata_ering *ering) return NULL; } -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 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; @@ -422,6 +418,17 @@ static int ata_ering_map(struct ata_ering *ering, return rc; } +int ata_ering_clear_cb(struct ata_ering_entry *ent, void *void_arg) +{ + ent->eflags |= ATA_EFLAG_OLD_ER; + return 0; +} + +static void ata_ering_clear(struct ata_ering *ering) +{ + ata_ering_map(ering, ata_ering_clear_cb, NULL); +} + static unsigned int ata_eh_dev_action(struct ata_device *dev) { struct ata_eh_context *ehc = &dev->link->eh_context; @@ -572,19 +579,19 @@ void ata_scsi_error(struct Scsi_Host *host) int nr_timedout = 0; spin_lock_irqsave(ap->lock, flags); - + /* This must occur under the ap->lock as we don't want a polled recovery to race the real interrupt handler - + The lost_interrupt handler checks for any completed but non-notified command and completes much like an IRQ handler. - + We then fall into the error recovery code which will treat this as if normal completion won the race */ if (ap->ops->lost_interrupt) ap->ops->lost_interrupt(ap); - + list_for_each_entry_safe(scmd, tmp, &host->eh_cmd_q, eh_entry) { struct ata_queued_cmd *qc; @@ -628,7 +635,7 @@ void ata_scsi_error(struct Scsi_Host *host) ap->eh_tries = ATA_EH_MAX_TRIES; } else spin_unlock_wait(ap->lock); - + /* If we timed raced normal completion and there is nothing to recover nr_timedout == 0 why exactly are we doing error recovery ? */ @@ -1755,7 +1762,7 @@ static int speed_down_verdict_cb(struct ata_ering_entry *ent, void *void_arg) struct speed_down_verdict_arg *arg = void_arg; int cat; - if (ent->timestamp < arg->since) + if ((ent->eflags & ATA_EFLAG_OLD_ER) || (ent->timestamp < arg->since)) return -1; cat = ata_eh_categorize_error(ent->eflags, ent->err_mask, diff --git a/drivers/ata/libata-pmp.c b/drivers/ata/libata-pmp.c index 224faabd7b7e..505470237d79 100644 --- a/drivers/ata/libata-pmp.c +++ b/drivers/ata/libata-pmp.c @@ -11,6 +11,7 @@ #include #include #include "libata.h" +#include "libata-transport.h" const struct ata_port_operations sata_pmp_port_ops = { .inherits = &sata_port_ops, @@ -312,10 +313,10 @@ static int sata_pmp_configure(struct ata_device *dev, int print_info) return rc; } -static int sata_pmp_init_links(struct ata_port *ap, int nr_ports) +static int sata_pmp_init_links (struct ata_port *ap, int nr_ports) { struct ata_link *pmp_link = ap->pmp_link; - int i; + int i, err; if (!pmp_link) { pmp_link = kzalloc(sizeof(pmp_link[0]) * SATA_PMP_MAX_PORTS, @@ -327,6 +328,13 @@ static int sata_pmp_init_links(struct ata_port *ap, int nr_ports) ata_link_init(ap, &pmp_link[i], i); ap->pmp_link = pmp_link; + + for (i = 0; i < SATA_PMP_MAX_PORTS; i++) { + err = ata_tlink_add(&pmp_link[i]); + if (err) { + goto err_tlink; + } + } } for (i = 0; i < nr_ports; i++) { @@ -339,6 +347,12 @@ static int sata_pmp_init_links(struct ata_port *ap, int nr_ports) } return 0; + err_tlink: + while (--i >= 0) + ata_tlink_delete(&pmp_link[i]); + kfree(pmp_link); + ap->pmp_link = NULL; + return err; } static void sata_pmp_quirks(struct ata_port *ap) diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index a89172c100f5..c16f5c151735 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -51,6 +51,7 @@ #include #include "libata.h" +#include "libata-transport.h" #define SECTOR_SIZE 512 #define ATA_SCSI_RBUF_SIZE 4096 @@ -64,9 +65,6 @@ static struct ata_device *__ata_scsi_find_dev(struct ata_port *ap, const struct scsi_device *scsidev); static struct ata_device *ata_scsi_find_dev(struct ata_port *ap, const struct scsi_device *scsidev); -static int ata_scsi_user_scan(struct Scsi_Host *shost, unsigned int channel, - unsigned int id, unsigned int lun); - #define RW_RECOVERY_MPAGE 0x1 #define RW_RECOVERY_MPAGE_LEN 12 @@ -106,17 +104,6 @@ static const u8 def_control_mpage[CONTROL_MPAGE_LEN] = { 0, 30 /* extended self test time, see 05-359r1 */ }; -/* - * libata transport template. libata doesn't do real transport stuff. - * It just needs the eh_timed_out hook. - */ -static struct scsi_transport_template ata_scsi_transport_template = { - .eh_strategy_handler = ata_scsi_error, - .eh_timed_out = ata_scsi_timed_out, - .user_scan = ata_scsi_user_scan, -}; - - static const struct { enum link_pm value; const char *name; @@ -3334,7 +3321,7 @@ int ata_scsi_add_hosts(struct ata_host *host, struct scsi_host_template *sht) *(struct ata_port **)&shost->hostdata[0] = ap; ap->scsi_host = shost; - shost->transportt = &ata_scsi_transport_template; + shost->transportt = ata_scsi_transport_template; shost->unique_id = ap->print_id; shost->max_id = 16; shost->max_lun = 1; @@ -3616,8 +3603,8 @@ void ata_scsi_hotplug(struct work_struct *work) * RETURNS: * Zero. */ -static int ata_scsi_user_scan(struct Scsi_Host *shost, unsigned int channel, - unsigned int id, unsigned int lun) +int ata_scsi_user_scan(struct Scsi_Host *shost, unsigned int channel, + unsigned int id, unsigned int lun) { struct ata_port *ap = ata_shost_to_port(shost); unsigned long flags; diff --git a/drivers/ata/libata-transport.c b/drivers/ata/libata-transport.c new file mode 100644 index 000000000000..a80860f537e4 --- /dev/null +++ b/drivers/ata/libata-transport.c @@ -0,0 +1,773 @@ +/* + * Copyright 2008 ioogle, Inc. All rights reserved. + * Released under GPL v2. + * + * Libata transport class. + * + * The ATA transport class contains common code to deal with ATA HBAs, + * an approximated representation of ATA topologies in the driver model, + * and various sysfs attributes to expose these topologies and management + * interfaces to user-space. + * + * There are 3 objects defined in in this class: + * - ata_port + * - ata_link + * - ata_device + * Each port has a link object. Each link can have up to two devices for PATA + * and generally one for SATA. + * If there is SATA port multiplier [PMP], 15 additional ata_link object are + * created. + * + * These objects are created when the ata host is initialized and when a PMP is + * found. They are removed only when the HBA is removed, cleaned before the + * error handler runs. + */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "libata.h" +#include "libata-transport.h" + +#define ATA_PORT_ATTRS 2 +#define ATA_LINK_ATTRS 3 +#define ATA_DEV_ATTRS 9 + +struct scsi_transport_template; +struct scsi_transport_template *ata_scsi_transport_template; + +struct ata_internal { + struct scsi_transport_template t; + + struct device_attribute private_port_attrs[ATA_PORT_ATTRS]; + struct device_attribute private_link_attrs[ATA_LINK_ATTRS]; + struct device_attribute private_dev_attrs[ATA_DEV_ATTRS]; + + struct transport_container link_attr_cont; + struct transport_container dev_attr_cont; + + /* + * The array of null terminated pointers to attributes + * needed by scsi_sysfs.c + */ + struct device_attribute *link_attrs[ATA_LINK_ATTRS + 1]; + struct device_attribute *port_attrs[ATA_PORT_ATTRS + 1]; + struct device_attribute *dev_attrs[ATA_DEV_ATTRS + 1]; +}; +#define to_ata_internal(tmpl) container_of(tmpl, struct ata_internal, t) + + +#define tdev_to_device(d) \ + container_of((d), struct ata_device, tdev) +#define transport_class_to_dev(dev) \ + tdev_to_device((dev)->parent) + +#define tdev_to_link(d) \ + container_of((d), struct ata_link, tdev) +#define transport_class_to_link(dev) \ + tdev_to_link((dev)->parent) + +#define tdev_to_port(d) \ + container_of((d), struct ata_port, tdev) +#define transport_class_to_port(dev) \ + tdev_to_port((dev)->parent) + + +/* Device objects are always created whit link objects */ +static int ata_tdev_add(struct ata_device *dev); +static void ata_tdev_delete(struct ata_device *dev); + + +/* + * Hack to allow attributes of the same name in different objects. + */ +#define ATA_DEVICE_ATTR(_prefix,_name,_mode,_show,_store) \ + struct device_attribute device_attr_##_prefix##_##_name = \ + __ATTR(_name,_mode,_show,_store) + +#define ata_bitfield_name_match(title, table) \ +static ssize_t \ +get_ata_##title##_names(u32 table_key, char *buf) \ +{ \ + char *prefix = ""; \ + ssize_t len = 0; \ + int i; \ + \ + for (i = 0; i < ARRAY_SIZE(table); i++) { \ + if (table[i].value & table_key) { \ + len += sprintf(buf + len, "%s%s", \ + prefix, table[i].name); \ + prefix = ", "; \ + } \ + } \ + len += sprintf(buf + len, "\n"); \ + return len; \ +} + +#define ata_bitfield_name_search(title, table) \ +static ssize_t \ +get_ata_##title##_names(u32 table_key, char *buf) \ +{ \ + ssize_t len = 0; \ + int i; \ + \ + for (i = 0; i < ARRAY_SIZE(table); i++) { \ + if (table[i].value == table_key) { \ + len += sprintf(buf + len, "%s", \ + table[i].name); \ + break; \ + } \ + } \ + len += sprintf(buf + len, "\n"); \ + return len; \ +} + +static struct { + u32 value; + char *name; +} ata_class_names[] = { + { ATA_DEV_UNKNOWN, "unknown" }, + { ATA_DEV_ATA, "ata" }, + { ATA_DEV_ATA_UNSUP, "ata" }, + { ATA_DEV_ATAPI, "atapi" }, + { ATA_DEV_ATAPI_UNSUP, "atapi" }, + { ATA_DEV_PMP, "pmp" }, + { ATA_DEV_PMP_UNSUP, "pmp" }, + { ATA_DEV_SEMB, "semb" }, + { ATA_DEV_SEMB_UNSUP, "semb" }, + { ATA_DEV_NONE, "none" } +}; +ata_bitfield_name_search(class, ata_class_names) + + +static struct { + u32 value; + char *name; +} ata_err_names[] = { + { AC_ERR_DEV, "DeviceError" }, + { AC_ERR_HSM, "HostStateMachineError" }, + { AC_ERR_TIMEOUT, "Timeout" }, + { AC_ERR_MEDIA, "MediaError" }, + { AC_ERR_ATA_BUS, "BusError" }, + { AC_ERR_HOST_BUS, "HostBusError" }, + { AC_ERR_SYSTEM, "SystemError" }, + { AC_ERR_INVALID, "InvalidArg" }, + { AC_ERR_OTHER, "Unknown" }, + { AC_ERR_NODEV_HINT, "NoDeviceHint" }, + { AC_ERR_NCQ, "NCQError" } +}; +ata_bitfield_name_match(err, ata_err_names) + +static struct { + u32 value; + char *name; +} ata_xfer_names[] = { + { XFER_UDMA_7, "XFER_UDMA_7" }, + { XFER_UDMA_6, "XFER_UDMA_6" }, + { XFER_UDMA_5, "XFER_UDMA_5" }, + { XFER_UDMA_4, "XFER_UDMA_4" }, + { XFER_UDMA_3, "XFER_UDMA_3" }, + { XFER_UDMA_2, "XFER_UDMA_2" }, + { XFER_UDMA_1, "XFER_UDMA_1" }, + { XFER_UDMA_0, "XFER_UDMA_0" }, + { XFER_MW_DMA_4, "XFER_MW_DMA_4" }, + { XFER_MW_DMA_3, "XFER_MW_DMA_3" }, + { XFER_MW_DMA_2, "XFER_MW_DMA_2" }, + { XFER_MW_DMA_1, "XFER_MW_DMA_1" }, + { XFER_MW_DMA_0, "XFER_MW_DMA_0" }, + { XFER_SW_DMA_2, "XFER_SW_DMA_2" }, + { XFER_SW_DMA_1, "XFER_SW_DMA_1" }, + { XFER_SW_DMA_0, "XFER_SW_DMA_0" }, + { XFER_PIO_6, "XFER_PIO_6" }, + { XFER_PIO_5, "XFER_PIO_5" }, + { XFER_PIO_4, "XFER_PIO_4" }, + { XFER_PIO_3, "XFER_PIO_3" }, + { XFER_PIO_2, "XFER_PIO_2" }, + { XFER_PIO_1, "XFER_PIO_1" }, + { XFER_PIO_0, "XFER_PIO_0" }, + { XFER_PIO_SLOW, "XFER_PIO_SLOW" } +}; +ata_bitfield_name_match(xfer,ata_xfer_names) + +/* + * ATA Port attributes + */ +#define ata_port_show_simple(field, name, format_string, cast) \ +static ssize_t \ +show_ata_port_##name(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct ata_port *ap = transport_class_to_port(dev); \ + \ + return snprintf(buf, 20, format_string, cast ap->field); \ +} + +#define ata_port_simple_attr(field, name, format_string, type) \ + ata_port_show_simple(field, name, format_string, (type)) \ +static DEVICE_ATTR(name, S_IRUGO, show_ata_port_##name, NULL) + +ata_port_simple_attr(nr_pmp_links, nr_pmp_links, "%d\n", int); +ata_port_simple_attr(stats.idle_irq, idle_irq, "%ld\n", unsigned long); + +static DECLARE_TRANSPORT_CLASS(ata_port_class, + "ata_port", NULL, NULL, NULL); + +static void ata_tport_release(struct device *dev) +{ + put_device(dev->parent); +} + +/** + * ata_is_port -- check if a struct device represents a ATA port + * @dev: device to check + * + * Returns: + * %1 if the device represents a ATA Port, %0 else + */ +int ata_is_port(const struct device *dev) +{ + return dev->release == ata_tport_release; +} + +static int ata_tport_match(struct attribute_container *cont, + struct device *dev) +{ + if (!ata_is_port(dev)) + return 0; + return &ata_scsi_transport_template->host_attrs.ac == cont; +} + +/** + * ata_tport_delete -- remove ATA PORT + * @port: ATA PORT to remove + * + * Removes the specified ATA PORT. Remove the associated link as well. + */ +void ata_tport_delete(struct ata_port *ap) +{ + struct device *dev = &ap->tdev; + + ata_tlink_delete(&ap->link); + + transport_remove_device(dev); + device_del(dev); + transport_destroy_device(dev); + put_device(dev); +} + +/** ata_tport_add - initialize a transport ATA port structure + * + * @parent: parent device + * @ap: existing ata_port structure + * + * Initialize a ATA port structure for sysfs. It will be added to the device + * tree below the device specified by @parent which could be a PCI device. + * + * Returns %0 on success + */ +int ata_tport_add(struct device *parent, + struct ata_port *ap) +{ + int error; + struct device *dev = &ap->tdev; + + device_initialize(dev); + + dev->parent = get_device(parent); + dev->release = ata_tport_release; + dev_set_name(dev, "ata%d", ap->print_id); + transport_setup_device(dev); + error = device_add(dev); + if (error) { + goto tport_err; + } + + transport_add_device(dev); + transport_configure_device(dev); + + error = ata_tlink_add(&ap->link); + if (error) { + goto tport_link_err; + } + return 0; + + tport_link_err: + transport_remove_device(dev); + device_del(dev); + + tport_err: + transport_destroy_device(dev); + put_device(dev); + return error; +} + + +/* + * ATA link attributes + */ + + +#define ata_link_show_linkspeed(field) \ +static ssize_t \ +show_ata_link_##field(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct ata_link *link = transport_class_to_link(dev); \ + \ + return sprintf(buf,"%s\n", sata_spd_string(fls(link->field))); \ +} + +#define ata_link_linkspeed_attr(field) \ + ata_link_show_linkspeed(field) \ +static DEVICE_ATTR(field, S_IRUGO, show_ata_link_##field, NULL) + +ata_link_linkspeed_attr(hw_sata_spd_limit); +ata_link_linkspeed_attr(sata_spd_limit); +ata_link_linkspeed_attr(sata_spd); + + +static DECLARE_TRANSPORT_CLASS(ata_link_class, + "ata_link", NULL, NULL, NULL); + +static void ata_tlink_release(struct device *dev) +{ + put_device(dev->parent); +} + +/** + * ata_is_link -- check if a struct device represents a ATA link + * @dev: device to check + * + * Returns: + * %1 if the device represents a ATA link, %0 else + */ +int ata_is_link(const struct device *dev) +{ + return dev->release == ata_tlink_release; +} + +static int ata_tlink_match(struct attribute_container *cont, + struct device *dev) +{ + struct ata_internal* i = to_ata_internal(ata_scsi_transport_template); + if (!ata_is_link(dev)) + return 0; + return &i->link_attr_cont.ac == cont; +} + +/** + * ata_tlink_delete -- remove ATA LINK + * @port: ATA LINK to remove + * + * Removes the specified ATA LINK. remove associated ATA device(s) as well. + */ +void ata_tlink_delete(struct ata_link *link) +{ + struct device *dev = &link->tdev; + struct ata_device *ata_dev; + + ata_for_each_dev(ata_dev, link, ALL) { + ata_tdev_delete(ata_dev); + } + + transport_remove_device(dev); + device_del(dev); + transport_destroy_device(dev); + put_device(dev); +} + +/** + * ata_tlink_add -- initialize a transport ATA link structure + * @link: allocated ata_link structure. + * + * Initialize an ATA LINK structure for sysfs. It will be added in the + * device tree below the ATA PORT it belongs to. + * + * Returns %0 on success + */ +int ata_tlink_add(struct ata_link *link) +{ + struct device *dev = &link->tdev; + struct ata_port *ap = link->ap; + struct ata_device *ata_dev; + int error; + + device_initialize(dev); + dev->parent = get_device(&ap->tdev); + dev->release = ata_tlink_release; + if (ata_is_host_link(link)) + dev_set_name(dev, "link%d", ap->print_id); + else + dev_set_name(dev, "link%d.%d", ap->print_id, link->pmp); + + transport_setup_device(dev); + + error = device_add(dev); + if (error) { + goto tlink_err; + } + + transport_add_device(dev); + transport_configure_device(dev); + + ata_for_each_dev(ata_dev, link, ALL) { + error = ata_tdev_add(ata_dev); + if (error) { + goto tlink_dev_err; + } + } + return 0; + tlink_dev_err: + while (--ata_dev >= link->device) { + ata_tdev_delete(ata_dev); + } + transport_remove_device(dev); + device_del(dev); + tlink_err: + transport_destroy_device(dev); + put_device(dev); + return error; +} + +/* + * ATA device attributes + */ + +#define ata_dev_show_class(title, field) \ +static ssize_t \ +show_ata_dev_##field(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct ata_device *ata_dev = transport_class_to_dev(dev); \ + \ + return get_ata_##title##_names(ata_dev->field, buf); \ +} + +#define ata_dev_attr(title, field) \ + ata_dev_show_class(title, field) \ +static DEVICE_ATTR(field, S_IRUGO, show_ata_dev_##field, NULL) + +ata_dev_attr(class, class); +ata_dev_attr(xfer, pio_mode); +ata_dev_attr(xfer, dma_mode); +ata_dev_attr(xfer, xfer_mode); + + +#define ata_dev_show_simple(field, format_string, cast) \ +static ssize_t \ +show_ata_dev_##field(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct ata_device *ata_dev = transport_class_to_dev(dev); \ + \ + return snprintf(buf, 20, format_string, cast ata_dev->field); \ +} + +#define ata_dev_simple_attr(field, format_string, type) \ + ata_dev_show_simple(field, format_string, (type)) \ +static DEVICE_ATTR(field, S_IRUGO, \ + show_ata_dev_##field, NULL) + +ata_dev_simple_attr(spdn_cnt, "%d\n", int); + +struct ata_show_ering_arg { + char* buf; + int written; +}; + +static int ata_show_ering(struct ata_ering_entry *ent, void *void_arg) +{ + struct ata_show_ering_arg* arg = void_arg; + struct timespec time; + + jiffies_to_timespec(ent->timestamp,&time); + arg->written += sprintf(arg->buf + arg->written, + "[%5lu.%06lu]", + time.tv_sec, time.tv_nsec); + arg->written += get_ata_err_names(ent->err_mask, + arg->buf + arg->written); + return 0; +} + +static ssize_t +show_ata_dev_ering(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ata_device *ata_dev = transport_class_to_dev(dev); + struct ata_show_ering_arg arg = { buf, 0 }; + + ata_ering_map(&ata_dev->ering, ata_show_ering, &arg); + return arg.written; +} + + +static DEVICE_ATTR(ering, S_IRUGO, show_ata_dev_ering, NULL); + +static ssize_t +show_ata_dev_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ata_device *ata_dev = transport_class_to_dev(dev); + int written = 0, i = 0; + + if (ata_dev->class == ATA_DEV_PMP) + return 0; + for(i=0;iid[i], + ((i+1) & 7) ? ' ' : '\n'); + } + return written; +} + +static DEVICE_ATTR(id, S_IRUGO, show_ata_dev_id, NULL); + +static ssize_t +show_ata_dev_gscr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ata_device *ata_dev = transport_class_to_dev(dev); + int written = 0, i = 0; + + if (ata_dev->class != ATA_DEV_PMP) + return 0; + for(i=0;igscr[i], + ((i+1) & 3) ? ' ' : '\n'); + } + if (SATA_PMP_GSCR_DWORDS & 3) + buf[written-1] = '\n'; + return written; +} + +static DEVICE_ATTR(gscr, S_IRUGO, show_ata_dev_gscr, NULL); + +static DECLARE_TRANSPORT_CLASS(ata_dev_class, + "ata_device", NULL, NULL, NULL); + +static void ata_tdev_release(struct device *dev) +{ + put_device(dev->parent); +} + +/** + * ata_is_ata_dev -- check if a struct device represents a ATA device + * @dev: device to check + * + * Returns: + * %1 if the device represents a ATA device, %0 else + */ +int ata_is_ata_dev(const struct device *dev) +{ + return dev->release == ata_tdev_release; +} + +static int ata_tdev_match(struct attribute_container *cont, + struct device *dev) +{ + struct ata_internal* i = to_ata_internal(ata_scsi_transport_template); + if (!ata_is_ata_dev(dev)) + return 0; + return &i->dev_attr_cont.ac == cont; +} + +/** + * ata_tdev_free -- free a ATA LINK + * @dev: ATA PHY to free + * + * Frees the specified ATA PHY. + * + * Note: + * This function must only be called on a PHY that has not + * successfully been added using ata_tdev_add(). + */ +static void ata_tdev_free(struct ata_device *dev) +{ + transport_destroy_device(&dev->tdev); + put_device(&dev->tdev); +} + +/** + * ata_tdev_delete -- remove ATA device + * @port: ATA PORT to remove + * + * Removes the specified ATA device. + */ +static void ata_tdev_delete(struct ata_device *ata_dev) +{ + struct device *dev = &ata_dev->tdev; + + transport_remove_device(dev); + device_del(dev); + ata_tdev_free(ata_dev); +} + + +/** + * ata_tdev_add -- initialize a transport ATA device structure. + * @ata_dev: ata_dev structure. + * + * Initialize an ATA device structure for sysfs. It will be added in the + * device tree below the ATA LINK device it belongs to. + * + * Returns %0 on success + */ +static int ata_tdev_add(struct ata_device *ata_dev) +{ + struct device *dev = &ata_dev->tdev; + struct ata_link *link = ata_dev->link; + struct ata_port *ap = link->ap; + int error; + + device_initialize(dev); + dev->parent = get_device(&link->tdev); + dev->release = ata_tdev_release; + if (ata_is_host_link(link)) + dev_set_name(dev, "dev%d.%d", ap->print_id,ata_dev->devno); + else + dev_set_name(dev, "dev%d.%d.0", ap->print_id, link->pmp); + + transport_setup_device(dev); + error = device_add(dev); + if (error) { + ata_tdev_free(ata_dev); + return error; + } + + transport_add_device(dev); + transport_configure_device(dev); + return 0; +} + + +/* + * Setup / Teardown code + */ + +#define SETUP_TEMPLATE(attrb, field, perm, test) \ + i->private_##attrb[count] = dev_attr_##field; \ + i->private_##attrb[count].attr.mode = perm; \ + i->attrb[count] = &i->private_##attrb[count]; \ + if (test) \ + count++ + +#define SETUP_LINK_ATTRIBUTE(field) \ + SETUP_TEMPLATE(link_attrs, field, S_IRUGO, 1) + +#define SETUP_PORT_ATTRIBUTE(field) \ + SETUP_TEMPLATE(port_attrs, field, S_IRUGO, 1) + +#define SETUP_DEV_ATTRIBUTE(field) \ + SETUP_TEMPLATE(dev_attrs, field, S_IRUGO, 1) + +/** + * ata_attach_transport -- instantiate ATA transport template + */ +struct scsi_transport_template *ata_attach_transport(void) +{ + struct ata_internal *i; + int count; + + i = kzalloc(sizeof(struct ata_internal), GFP_KERNEL); + if (!i) + return NULL; + + i->t.eh_strategy_handler = ata_scsi_error; + i->t.eh_timed_out = ata_scsi_timed_out; + i->t.user_scan = ata_scsi_user_scan; + + i->t.host_attrs.ac.attrs = &i->port_attrs[0]; + i->t.host_attrs.ac.class = &ata_port_class.class; + i->t.host_attrs.ac.match = ata_tport_match; + transport_container_register(&i->t.host_attrs); + + i->link_attr_cont.ac.class = &ata_link_class.class; + i->link_attr_cont.ac.attrs = &i->link_attrs[0]; + i->link_attr_cont.ac.match = ata_tlink_match; + transport_container_register(&i->link_attr_cont); + + i->dev_attr_cont.ac.class = &ata_dev_class.class; + i->dev_attr_cont.ac.attrs = &i->dev_attrs[0]; + i->dev_attr_cont.ac.match = ata_tdev_match; + transport_container_register(&i->dev_attr_cont); + + count = 0; + SETUP_PORT_ATTRIBUTE(nr_pmp_links); + SETUP_PORT_ATTRIBUTE(idle_irq); + BUG_ON(count > ATA_PORT_ATTRS); + i->port_attrs[count] = NULL; + + count = 0; + SETUP_LINK_ATTRIBUTE(hw_sata_spd_limit); + SETUP_LINK_ATTRIBUTE(sata_spd_limit); + SETUP_LINK_ATTRIBUTE(sata_spd); + BUG_ON(count > ATA_LINK_ATTRS); + i->link_attrs[count] = NULL; + + count = 0; + SETUP_DEV_ATTRIBUTE(class); + SETUP_DEV_ATTRIBUTE(pio_mode); + SETUP_DEV_ATTRIBUTE(dma_mode); + SETUP_DEV_ATTRIBUTE(xfer_mode); + SETUP_DEV_ATTRIBUTE(spdn_cnt); + SETUP_DEV_ATTRIBUTE(ering); + SETUP_DEV_ATTRIBUTE(id); + SETUP_DEV_ATTRIBUTE(gscr); + BUG_ON(count > ATA_DEV_ATTRS); + i->dev_attrs[count] = NULL; + + return &i->t; +} + +/** + * ata_release_transport -- release ATA transport template instance + * @t: transport template instance + */ +void ata_release_transport(struct scsi_transport_template *t) +{ + struct ata_internal *i = to_ata_internal(t); + + transport_container_unregister(&i->t.host_attrs); + transport_container_unregister(&i->link_attr_cont); + transport_container_unregister(&i->dev_attr_cont); + + kfree(i); +} + +__init int libata_transport_init(void) +{ + int error; + + error = transport_class_register(&ata_link_class); + if (error) + goto out_unregister_transport; + error = transport_class_register(&ata_port_class); + if (error) + goto out_unregister_link; + error = transport_class_register(&ata_dev_class); + if (error) + goto out_unregister_port; + return 0; + + out_unregister_port: + transport_class_unregister(&ata_port_class); + out_unregister_link: + transport_class_unregister(&ata_link_class); + out_unregister_transport: + return error; + +} + +void __exit libata_transport_exit(void) +{ + transport_class_unregister(&ata_link_class); + transport_class_unregister(&ata_port_class); + transport_class_unregister(&ata_dev_class); +} diff --git a/drivers/ata/libata-transport.h b/drivers/ata/libata-transport.h new file mode 100644 index 000000000000..2820cf864f11 --- /dev/null +++ b/drivers/ata/libata-transport.h @@ -0,0 +1,18 @@ +#ifndef _LIBATA_TRANSPORT_H +#define _LIBATA_TRANSPORT_H + + +extern struct scsi_transport_template *ata_scsi_transport_template; + +int ata_tlink_add(struct ata_link *link); +void ata_tlink_delete(struct ata_link *link); + +int ata_tport_add(struct device *parent, struct ata_port *ap); +void ata_tport_delete(struct ata_port *ap); + +struct scsi_transport_template *ata_attach_transport(void); +void ata_release_transport(struct scsi_transport_template *t); + +__init int libata_transport_init(void); +void __exit libata_transport_exit(void); +#endif diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 9ce1ecc63e39..142102b94df5 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -102,6 +102,7 @@ extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg); extern struct ata_port *ata_port_alloc(struct ata_host *host); extern void ata_dev_enable_pm(struct ata_device *dev, enum link_pm policy); extern void ata_lpm_schedule(struct ata_port *ap, enum link_pm); +extern const char *sata_spd_string(unsigned int spd); /* libata-acpi.c */ #ifdef CONFIG_ATA_ACPI @@ -137,6 +138,9 @@ extern void ata_scsi_hotplug(struct work_struct *work); extern void ata_schedule_scsi_eh(struct Scsi_Host *shost); extern void ata_scsi_dev_rescan(struct work_struct *work); extern int ata_bus_probe(struct ata_port *ap); +extern int ata_scsi_user_scan(struct Scsi_Host *shost, unsigned int channel, + unsigned int id, unsigned int lun); + /* libata-eh.c */ extern unsigned long ata_internal_cmd_timeout(struct ata_device *dev, u8 cmd); @@ -164,6 +168,9 @@ extern int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, ata_postreset_fn_t postreset, struct ata_link **r_failed_disk); extern void ata_eh_finish(struct ata_port *ap); +extern int ata_ering_map(struct ata_ering *ering, + int (*map_fn)(struct ata_ering_entry *, void *), + void *arg); /* libata-pmp.c */ #ifdef CONFIG_SATA_PMP diff --git a/include/linux/libata.h b/include/linux/libata.h index 45fb2967b66d..c50f66d4382c 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -604,6 +604,7 @@ struct ata_device { union acpi_object *gtf_cache; unsigned int gtf_filter; #endif + struct device tdev; /* n_sector is CLEAR_BEGIN, read comment above CLEAR_BEGIN */ u64 n_sectors; /* size of device, if ATA */ u64 n_native_sectors; /* native size, if ATA */ @@ -690,6 +691,7 @@ struct ata_link { struct ata_port *ap; int pmp; /* port multiplier port # */ + struct device tdev; unsigned int active_tag; /* active tag on this link */ u32 sactive; /* active NCQ commands */ @@ -707,6 +709,8 @@ struct ata_link { struct ata_device device[ATA_MAX_DEVICES]; }; +#define ATA_LINK_CLEAR_BEGIN offsetof(struct ata_link, active_tag) +#define ATA_LINK_CLEAR_END offsetof(struct ata_link, device[0]) struct ata_port { struct Scsi_Host *scsi_host; /* our co-allocated scsi host */ @@ -752,6 +756,7 @@ struct ata_port { struct ata_port_stats stats; struct ata_host *host; struct device *dev; + struct device tdev; struct mutex scsi_scan_mutex; struct delayed_work hotplug_task; -- cgit v1.2.3 From c93b263e0d4fa8ce5fec0142a98196d1a127e845 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 1 Sep 2010 17:50:04 +0200 Subject: libata: clean up lpm related symbols and sysfs show/store functions Link power management related symbols are in confusing state w/ mixed usages of lpm, ipm and pm. This patch cleans up lpm related symbols and sysfs show/store functions as follows. * lpm states - NOT_AVAILABLE, MIN_POWER, MAX_PERFORMANCE and MEDIUM_POWER are renamed to ATA_LPM_UNKNOWN and ATA_LPM_{MIN|MAX|MED}_POWER. * Pre/postfixes are unified to lpm. * sysfs show/store functions for link_power_management_policy were curiously named get/put and unnecessarily complex. Renamed to show/store and simplified. Signed-off-by: Tejun Heo Signed-off-by: Jeff Garzik --- drivers/ata/ahci.c | 2 +- drivers/ata/ahci.h | 2 +- drivers/ata/ahci_platform.c | 2 +- drivers/ata/libahci.c | 20 +++++++------- drivers/ata/libata-core.c | 50 +++++++++++++++++----------------- drivers/ata/libata-eh.c | 2 +- drivers/ata/libata-scsi.c | 66 +++++++++++++++------------------------------ drivers/ata/libata.h | 6 +++-- include/linux/libata.h | 29 ++++++++++---------- 9 files changed, 79 insertions(+), 100 deletions(-) (limited to 'include/linux/libata.h') diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index 99d0e5a51148..68f7b4216501 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -1209,7 +1209,7 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) 0x100 + ap->port_no * 0x80, "port"); /* set initial link pm policy */ - ap->pm_policy = NOT_AVAILABLE; + ap->lpm_policy = ATA_LPM_UNKNOWN; /* set enclosure management message type */ if (ap->flags & ATA_FLAG_EM) diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h index e5fdeebf9ef0..93867d3f8dd3 100644 --- a/drivers/ata/ahci.h +++ b/drivers/ata/ahci.h @@ -216,7 +216,7 @@ enum { AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | ATA_FLAG_ACPI_SATA | ATA_FLAG_AN | - ATA_FLAG_IPM, + ATA_FLAG_LPM, ICH_MAP = 0x90, /* ICH MAP register */ diff --git a/drivers/ata/ahci_platform.c b/drivers/ata/ahci_platform.c index 84b643270e7a..07556853c0d6 100644 --- a/drivers/ata/ahci_platform.c +++ b/drivers/ata/ahci_platform.c @@ -130,7 +130,7 @@ static int __init ahci_probe(struct platform_device *pdev) ata_port_desc(ap, "port 0x%x", 0x100 + ap->port_no * 0x80); /* set initial link pm policy */ - ap->pm_policy = NOT_AVAILABLE; + ap->lpm_policy = ATA_LPM_UNKNOWN; /* set enclosure management message type */ if (ap->flags & ATA_FLAG_EM) diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index 8eea309ea212..ed7803f2b4f1 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -56,8 +56,7 @@ MODULE_PARM_DESC(skip_host_reset, "skip global host reset (0=don't skip, 1=skip) module_param_named(ignore_sss, ahci_ignore_sss, int, 0444); MODULE_PARM_DESC(ignore_sss, "Ignore staggered spinup flag (0=don't ignore, 1=ignore)"); -static int ahci_enable_alpm(struct ata_port *ap, - enum link_pm policy); +static int ahci_enable_alpm(struct ata_port *ap, enum ata_lpm_policy policy); static void ahci_disable_alpm(struct ata_port *ap); static ssize_t ahci_led_show(struct ata_port *ap, char *buf); static ssize_t ahci_led_store(struct ata_port *ap, const char *buf, @@ -649,7 +648,7 @@ static void ahci_disable_alpm(struct ata_port *ap) u32 cmd; struct ahci_port_priv *pp = ap->private_data; - /* IPM bits should be disabled by libata-core */ + /* LPM bits should be disabled by libata-core */ /* get the existing command bits */ cmd = readl(port_mmio + PORT_CMD); @@ -691,8 +690,7 @@ static void ahci_disable_alpm(struct ata_port *ap) */ } -static int ahci_enable_alpm(struct ata_port *ap, - enum link_pm policy) +static int ahci_enable_alpm(struct ata_port *ap, enum ata_lpm_policy policy) { struct ahci_host_priv *hpriv = ap->host->private_data; void __iomem *port_mmio = ahci_port_base(ap); @@ -705,21 +703,21 @@ static int ahci_enable_alpm(struct ata_port *ap, return -EINVAL; switch (policy) { - case MAX_PERFORMANCE: - case NOT_AVAILABLE: + case ATA_LPM_MAX_POWER: + case ATA_LPM_UNKNOWN: /* - * if we came here with NOT_AVAILABLE, + * if we came here with ATA_LPM_UNKNOWN, * it just means this is the first time we * have tried to enable - default to max performance, * and let the user go to lower power modes on request. */ ahci_disable_alpm(ap); return 0; - case MIN_POWER: + case ATA_LPM_MIN_POWER: /* configure HBA to enter SLUMBER */ asp = PORT_CMD_ASP; break; - case MEDIUM_POWER: + case ATA_LPM_MED_POWER: /* configure HBA to enter PARTIAL */ asp = 0; break; @@ -762,7 +760,7 @@ static int ahci_enable_alpm(struct ata_port *ap, writel(cmd, port_mmio + PORT_CMD); cmd = readl(port_mmio + PORT_CMD); - /* IPM bits should be set by libata-core */ + /* LPM bits should be set by libata-core */ return 0; } diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 92cd5f375b8f..380ceb000aad 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -1030,7 +1030,7 @@ const char *sata_spd_string(unsigned int spd) return spd_str[spd - 1]; } -static int ata_dev_set_dipm(struct ata_device *dev, enum link_pm policy) +static int ata_dev_set_dipm(struct ata_device *dev, enum ata_lpm_policy policy) { struct ata_link *link = dev->link; struct ata_port *ap = link->ap; @@ -1040,14 +1040,14 @@ static int ata_dev_set_dipm(struct ata_device *dev, enum link_pm policy) /* * disallow DIPM for drivers which haven't set - * ATA_FLAG_IPM. This is because when DIPM is enabled, + * ATA_FLAG_LPM. This is because when DIPM is enabled, * phy ready will be set in the interrupt status on * state changes, which will cause some drivers to * think there are errors - additionally drivers will * need to disable hot plug. */ - if (!(ap->flags & ATA_FLAG_IPM) || !ata_dev_enabled(dev)) { - ap->pm_policy = NOT_AVAILABLE; + if (!(ap->flags & ATA_FLAG_LPM) || !ata_dev_enabled(dev)) { + ap->lpm_policy = ATA_LPM_UNKNOWN; return -EINVAL; } @@ -1066,8 +1066,8 @@ static int ata_dev_set_dipm(struct ata_device *dev, enum link_pm policy) return rc; switch (policy) { - case MIN_POWER: - /* no restrictions on IPM transitions */ + case ATA_LPM_MIN_POWER: + /* no restrictions on LPM transitions */ scontrol &= ~(0x3 << 8); rc = sata_scr_write(link, SCR_CONTROL, scontrol); if (rc) @@ -1078,8 +1078,8 @@ static int ata_dev_set_dipm(struct ata_device *dev, enum link_pm policy) err_mask = ata_dev_set_feature(dev, SETFEATURES_SATA_ENABLE, SATA_DIPM); break; - case MEDIUM_POWER: - /* allow IPM to PARTIAL */ + case ATA_LPM_MED_POWER: + /* allow LPM to PARTIAL */ scontrol &= ~(0x1 << 8); scontrol |= (0x2 << 8); rc = sata_scr_write(link, SCR_CONTROL, scontrol); @@ -1087,21 +1087,21 @@ static int ata_dev_set_dipm(struct ata_device *dev, enum link_pm policy) return rc; /* - * we don't have to disable DIPM since IPM flags + * we don't have to disable DIPM since LPM flags * disallow transitions to SLUMBER, which effectively * disable DIPM if it does not support PARTIAL */ break; - case NOT_AVAILABLE: - case MAX_PERFORMANCE: - /* disable all IPM transitions */ + case ATA_LPM_UNKNOWN: + case ATA_LPM_MAX_POWER: + /* disable all LPM transitions */ scontrol |= (0x3 << 8); rc = sata_scr_write(link, SCR_CONTROL, scontrol); if (rc) return rc; /* - * we don't have to disable DIPM since IPM flags + * we don't have to disable DIPM since LPM flags * disallow all transitions which effectively * disable DIPM anyway. */ @@ -1125,9 +1125,9 @@ static int ata_dev_set_dipm(struct ata_device *dev, enum link_pm policy) * enabling Host Initiated Power management. * * Locking: Caller. - * Returns: -EINVAL if IPM is not supported, 0 otherwise. + * Returns: -EINVAL if LPM is not supported, 0 otherwise. */ -void ata_dev_enable_pm(struct ata_device *dev, enum link_pm policy) +void ata_dev_enable_pm(struct ata_device *dev, enum ata_lpm_policy policy) { int rc = 0; struct ata_port *ap = dev->link->ap; @@ -1141,9 +1141,9 @@ void ata_dev_enable_pm(struct ata_device *dev, enum link_pm policy) enable_pm_out: if (rc) - ap->pm_policy = MAX_PERFORMANCE; + ap->lpm_policy = ATA_LPM_MAX_POWER; else - ap->pm_policy = policy; + ap->lpm_policy = policy; return /* rc */; /* hopefully we can use 'rc' eventually */ } @@ -1164,15 +1164,15 @@ static void ata_dev_disable_pm(struct ata_device *dev) { struct ata_port *ap = dev->link->ap; - ata_dev_set_dipm(dev, MAX_PERFORMANCE); + ata_dev_set_dipm(dev, ATA_LPM_MAX_POWER); if (ap->ops->disable_pm) ap->ops->disable_pm(ap); } #endif /* CONFIG_PM */ -void ata_lpm_schedule(struct ata_port *ap, enum link_pm policy) +void ata_lpm_schedule(struct ata_port *ap, enum ata_lpm_policy policy) { - ap->pm_policy = policy; + ap->lpm_policy = policy; ap->link.eh_info.action |= ATA_EH_LPM; ap->link.eh_info.flags |= ATA_EHI_NO_AUTOPSY; ata_port_schedule_eh(ap); @@ -1201,7 +1201,7 @@ static void ata_lpm_disable(struct ata_host *host) for (i = 0; i < host->n_ports; i++) { struct ata_port *ap = host->ports[i]; - ata_lpm_schedule(ap, ap->pm_policy); + ata_lpm_schedule(ap, ap->lpm_policy); } } #endif /* CONFIG_PM */ @@ -2564,7 +2564,7 @@ int ata_dev_configure(struct ata_device *dev) if (dev->flags & ATA_DFLAG_LBA48) dev->max_sectors = ATA_MAX_SECTORS_LBA48; - if (!(dev->horkage & ATA_HORKAGE_IPM)) { + if (!(dev->horkage & ATA_HORKAGE_LPM)) { if (ata_id_has_hipm(dev->id)) dev->flags |= ATA_DFLAG_HIPM; if (ata_id_has_dipm(dev->id)) @@ -2591,11 +2591,11 @@ int ata_dev_configure(struct ata_device *dev) dev->max_sectors = min_t(unsigned int, ATA_MAX_SECTORS_128, dev->max_sectors); - if (ata_dev_blacklisted(dev) & ATA_HORKAGE_IPM) { - dev->horkage |= ATA_HORKAGE_IPM; + if (ata_dev_blacklisted(dev) & ATA_HORKAGE_LPM) { + dev->horkage |= ATA_HORKAGE_LPM; /* reset link pm_policy for this port to no pm */ - ap->pm_policy = MAX_PERFORMANCE; + ap->lpm_policy = ATA_LPM_MAX_POWER; } if (ap->ops->dev_config) diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 95838b382231..96aa75ddf87f 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -3570,7 +3570,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, /* configure link power saving */ if (ehc->i.action & ATA_EH_LPM) ata_for_each_dev(dev, link, ALL) - ata_dev_enable_pm(dev, ap->pm_policy); + ata_dev_enable_pm(dev, ap->lpm_policy); /* this link is okay now */ ehc->i.flags = 0; diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index f1c0118c6d4b..aa56681f68db 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -103,72 +103,50 @@ static const u8 def_control_mpage[CONTROL_MPAGE_LEN] = { 0, 30 /* extended self test time, see 05-359r1 */ }; -static const struct { - enum link_pm value; - const char *name; -} link_pm_policy[] = { - { NOT_AVAILABLE, "max_performance" }, - { MIN_POWER, "min_power" }, - { MAX_PERFORMANCE, "max_performance" }, - { MEDIUM_POWER, "medium_power" }, +static const char *ata_lpm_policy_names[] = { + [ATA_LPM_UNKNOWN] = "max_performance", + [ATA_LPM_MAX_POWER] = "max_performance", + [ATA_LPM_MED_POWER] = "medium_power", + [ATA_LPM_MIN_POWER] = "min_power", }; -static const char *ata_scsi_lpm_get(enum link_pm policy) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(link_pm_policy); i++) - if (link_pm_policy[i].value == policy) - return link_pm_policy[i].name; - - return NULL; -} - -static ssize_t ata_scsi_lpm_put(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) +static ssize_t ata_scsi_lpm_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { struct Scsi_Host *shost = class_to_shost(dev); struct ata_port *ap = ata_shost_to_port(shost); - enum link_pm policy = 0; - int i; + enum ata_lpm_policy policy; - /* - * we are skipping array location 0 on purpose - this - * is because a value of NOT_AVAILABLE is displayed - * to the user as max_performance, but when the user - * writes "max_performance", they actually want the - * value to match MAX_PERFORMANCE. - */ - for (i = 1; i < ARRAY_SIZE(link_pm_policy); i++) { - const int len = strlen(link_pm_policy[i].name); - if (strncmp(link_pm_policy[i].name, buf, len) == 0) { - policy = link_pm_policy[i].value; + /* UNKNOWN is internal state, iterate from MAX_POWER */ + for (policy = ATA_LPM_MAX_POWER; + policy < ARRAY_SIZE(ata_lpm_policy_names); policy++) { + const char *name = ata_lpm_policy_names[policy]; + + if (strncmp(name, buf, strlen(name)) == 0) break; - } } - if (!policy) + if (policy == ARRAY_SIZE(ata_lpm_policy_names)) return -EINVAL; ata_lpm_schedule(ap, policy); return count; } -static ssize_t -ata_scsi_lpm_show(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t ata_scsi_lpm_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct Scsi_Host *shost = class_to_shost(dev); struct ata_port *ap = ata_shost_to_port(shost); - const char *policy = - ata_scsi_lpm_get(ap->pm_policy); - if (!policy) + if (ap->lpm_policy >= ARRAY_SIZE(ata_lpm_policy_names)) return -EINVAL; - return snprintf(buf, 23, "%s\n", policy); + return snprintf(buf, PAGE_SIZE, "%s\n", + ata_lpm_policy_names[ap->lpm_policy]); } DEVICE_ATTR(link_power_management_policy, S_IRUGO | S_IWUSR, - ata_scsi_lpm_show, ata_scsi_lpm_put); + ata_scsi_lpm_show, ata_scsi_lpm_store); EXPORT_SYMBOL_GPL(dev_attr_link_power_management_policy); static ssize_t ata_scsi_park_show(struct device *device, diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 142102b94df5..1471462e3e3c 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -100,8 +100,10 @@ extern int sata_link_init_spd(struct ata_link *link); extern int ata_task_ioctl(struct scsi_device *scsidev, void __user *arg); extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg); extern struct ata_port *ata_port_alloc(struct ata_host *host); -extern void ata_dev_enable_pm(struct ata_device *dev, enum link_pm policy); -extern void ata_lpm_schedule(struct ata_port *ap, enum link_pm); +extern void ata_dev_enable_pm(struct ata_device *dev, + enum ata_lpm_policy policy); +extern void ata_lpm_schedule(struct ata_port *ap, + enum ata_lpm_policy policy); extern const char *sata_spd_string(unsigned int spd); /* libata-acpi.c */ diff --git a/include/linux/libata.h b/include/linux/libata.h index c50f66d4382c..c5bdc90fd319 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -196,7 +196,7 @@ enum { ATA_FLAG_ACPI_SATA = (1 << 17), /* need native SATA ACPI layout */ ATA_FLAG_AN = (1 << 18), /* controller supports AN */ ATA_FLAG_PMP = (1 << 19), /* controller supports PMP */ - ATA_FLAG_IPM = (1 << 20), /* driver can handle IPM */ + ATA_FLAG_LPM = (1 << 20), /* driver can handle LPM */ ATA_FLAG_EM = (1 << 21), /* driver supports enclosure * management */ ATA_FLAG_SW_ACTIVITY = (1 << 22), /* driver supports sw activity @@ -377,7 +377,7 @@ enum { ATA_HORKAGE_BROKEN_HPA = (1 << 4), /* Broken HPA */ ATA_HORKAGE_DISABLE = (1 << 5), /* Disable it */ ATA_HORKAGE_HPA_SIZE = (1 << 6), /* native size off by one */ - ATA_HORKAGE_IPM = (1 << 7), /* Link PM problems */ + ATA_HORKAGE_LPM = (1 << 7), /* Link PM problems */ ATA_HORKAGE_IVB = (1 << 8), /* cbl det validity bit bugs */ ATA_HORKAGE_STUCK_ERR = (1 << 9), /* stuck ERR on next PACKET */ ATA_HORKAGE_BRIDGE_OK = (1 << 10), /* no bridge limits */ @@ -464,6 +464,17 @@ enum ata_completion_errors { AC_ERR_NCQ = (1 << 10), /* marker for offending NCQ qc */ }; +/* + * Link power management policy: If you alter this, you also need to + * alter libata-scsi.c (for the ascii descriptions) + */ +enum ata_lpm_policy { + ATA_LPM_UNKNOWN, + ATA_LPM_MAX_POWER, + ATA_LPM_MED_POWER, + ATA_LPM_MIN_POWER, +}; + /* forward declarations */ struct scsi_device; struct ata_port_operations; @@ -478,16 +489,6 @@ typedef int (*ata_reset_fn_t)(struct ata_link *link, unsigned int *classes, unsigned long deadline); typedef void (*ata_postreset_fn_t)(struct ata_link *link, unsigned int *classes); -/* - * host pm policy: If you alter this, you also need to alter libata-scsi.c - * (for the ascii descriptions) - */ -enum link_pm { - NOT_AVAILABLE, - MIN_POWER, - MAX_PERFORMANCE, - MEDIUM_POWER, -}; extern struct device_attribute dev_attr_link_power_management_policy; extern struct device_attribute dev_attr_unload_heads; extern struct device_attribute dev_attr_em_message_type; @@ -772,7 +773,7 @@ struct ata_port { pm_message_t pm_mesg; int *pm_result; - enum link_pm pm_policy; + enum ata_lpm_policy lpm_policy; struct timer_list fastdrain_timer; unsigned long fastdrain_cnt; @@ -838,7 +839,7 @@ struct ata_port_operations { int (*scr_write)(struct ata_link *link, unsigned int sc_reg, u32 val); void (*pmp_attach)(struct ata_port *ap); void (*pmp_detach)(struct ata_port *ap); - int (*enable_pm)(struct ata_port *ap, enum link_pm policy); + int (*enable_pm)(struct ata_port *ap, enum ata_lpm_policy policy); void (*disable_pm)(struct ata_port *ap); /* -- cgit v1.2.3 From 1152b2617a6e1943b6b82e07c962950e56f1000c Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 1 Sep 2010 17:50:05 +0200 Subject: libata: implement sata_link_scr_lpm() and make ata_dev_set_feature() global Link power management is about to be reimplemented. Prepare for it. * Implement sata_link_scr_lpm(). * Drop static from ata_dev_set_feature() and make it available to other libata files. * Trivial whitespace adjustments. Signed-off-by: Tejun Heo Signed-off-by: Jeff Garzik --- drivers/ata/libata-core.c | 75 +++++++++++++++++++++++++++++++++++++++++++---- drivers/ata/libata.h | 2 ++ include/linux/libata.h | 2 ++ 3 files changed, 74 insertions(+), 5 deletions(-) (limited to 'include/linux/libata.h') diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 380ceb000aad..b8024451234c 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -91,8 +91,6 @@ const struct ata_port_operations sata_port_ops = { static unsigned int ata_dev_init_params(struct ata_device *dev, u16 heads, u16 sectors); static unsigned int ata_dev_set_xfermode(struct ata_device *dev); -static unsigned int ata_dev_set_feature(struct ata_device *dev, - u8 enable, u8 feature); static void ata_dev_xfermask(struct ata_device *dev); static unsigned long ata_dev_blacklisted(const struct ata_device *dev); @@ -3628,7 +3626,7 @@ int ata_wait_after_reset(struct ata_link *link, unsigned long deadline, * @params: timing parameters { interval, duratinon, timeout } in msec * @deadline: deadline jiffies for the operation * -* Make sure SStatus of @link reaches stable state, determined by + * Make sure SStatus of @link reaches stable state, determined by * holding the same value where DET is not 1 for @duration polled * every @interval, before @timeout. Timeout constraints the * beginning of the stable state. Because DET gets stuck at 1 on @@ -3759,6 +3757,72 @@ int sata_link_resume(struct ata_link *link, const unsigned long *params, return rc != -EINVAL ? rc : 0; } +/** + * sata_link_scr_lpm - manipulate SControl IPM and SPM fields + * @link: ATA link to manipulate SControl for + * @policy: LPM policy to configure + * @spm_wakeup: initiate LPM transition to active state + * + * Manipulate the IPM field of the SControl register of @link + * according to @policy. If @policy is ATA_LPM_MAX_POWER and + * @spm_wakeup is %true, the SPM field is manipulated to wake up + * the link. This function also clears PHYRDY_CHG before + * returning. + * + * LOCKING: + * EH context. + * + * RETURNS: + * 0 on succes, -errno otherwise. + */ +int sata_link_scr_lpm(struct ata_link *link, enum ata_lpm_policy policy, + bool spm_wakeup) +{ + struct ata_eh_context *ehc = &link->eh_context; + bool woken_up = false; + u32 scontrol; + int rc; + + rc = sata_scr_read(link, SCR_CONTROL, &scontrol); + if (rc) + return rc; + + switch (policy) { + case ATA_LPM_MAX_POWER: + /* disable all LPM transitions */ + scontrol |= (0x3 << 8); + /* initiate transition to active state */ + if (spm_wakeup) { + scontrol |= (0x4 << 12); + woken_up = true; + } + break; + case ATA_LPM_MED_POWER: + /* allow LPM to PARTIAL */ + scontrol &= ~(0x1 << 8); + scontrol |= (0x2 << 8); + break; + case ATA_LPM_MIN_POWER: + /* no restrictions on LPM transitions */ + scontrol &= ~(0x3 << 8); + break; + default: + WARN_ON(1); + } + + rc = sata_scr_write(link, SCR_CONTROL, scontrol); + if (rc) + return rc; + + /* give the link time to transit out of LPM state */ + if (woken_up) + msleep(10); + + /* clear PHYRDY_CHG from SError */ + ehc->i.serror &= ~SERR_PHYRDY_CHG; + return sata_scr_write(link, SCR_ERROR, SERR_PHYRDY_CHG); +} + /** * ata_std_prereset - prepare for reset * @link: ATA link to be reset @@ -4551,6 +4615,7 @@ static unsigned int ata_dev_set_xfermode(struct ata_device *dev) DPRINTK("EXIT, err_mask=%x\n", err_mask); return err_mask; } + /** * ata_dev_set_feature - Issue SET FEATURES - SATA FEATURES * @dev: Device to which command will be sent @@ -4566,8 +4631,7 @@ static unsigned int ata_dev_set_xfermode(struct ata_device *dev) * RETURNS: * 0 on success, AC_ERR_* mask otherwise. */ -static unsigned int ata_dev_set_feature(struct ata_device *dev, u8 enable, - u8 feature) +unsigned int ata_dev_set_feature(struct ata_device *dev, u8 enable, u8 feature) { struct ata_taskfile tf; unsigned int err_mask; @@ -6732,6 +6796,7 @@ EXPORT_SYMBOL_GPL(sata_set_spd); EXPORT_SYMBOL_GPL(ata_wait_after_reset); EXPORT_SYMBOL_GPL(sata_link_debounce); EXPORT_SYMBOL_GPL(sata_link_resume); +EXPORT_SYMBOL_GPL(sata_link_scr_lpm); EXPORT_SYMBOL_GPL(ata_std_prereset); EXPORT_SYMBOL_GPL(sata_link_hardreset); EXPORT_SYMBOL_GPL(sata_std_hardreset); diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 1471462e3e3c..65b6a73c3ac7 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -86,6 +86,8 @@ extern int ata_dev_revalidate(struct ata_device *dev, unsigned int new_class, extern int ata_dev_configure(struct ata_device *dev); extern int sata_down_spd_limit(struct ata_link *link, u32 spd_limit); extern int ata_down_xfermask_limit(struct ata_device *dev, unsigned int sel); +extern unsigned int ata_dev_set_feature(struct ata_device *dev, + u8 enable, u8 feature); extern void ata_sg_clean(struct ata_queued_cmd *qc); extern void ata_qc_free(struct ata_queued_cmd *qc); extern void ata_qc_issue(struct ata_queued_cmd *qc); diff --git a/include/linux/libata.h b/include/linux/libata.h index c5bdc90fd319..7770eeb21039 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -952,6 +952,8 @@ extern int sata_link_debounce(struct ata_link *link, const unsigned long *params, unsigned long deadline); extern int sata_link_resume(struct ata_link *link, const unsigned long *params, unsigned long deadline); +extern int sata_link_scr_lpm(struct ata_link *link, enum ata_lpm_policy policy, + bool spm_wakeup); extern int sata_link_hardreset(struct ata_link *link, const unsigned long *timing, unsigned long deadline, bool *online, int (*check_ready)(struct ata_link *)); -- cgit v1.2.3 From 6b7ae9545ad9875a289f4191c0216b473e313cb9 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Wed, 1 Sep 2010 17:50:06 +0200 Subject: libata: reimplement link power management The current LPM implementation has the following issues. * Operation order isn't well thought-out. e.g. HIPM should be configured after IPM in SControl is properly configured. Not the other way around. * Suspend/resume paths call ata_lpm_enable/disable() which must only be called from EH context directly. Also, ata_lpm_enable/disable() were called whether LPM was in use or not. * Implementation is per-port when it should be per-link. As a result, it can't be used for controllers with slave links or PMP. * LPM state isn't managed consistently. After a link reset for whatever reason including suspend/resume the actual LPM state would be reset leaving ap->lpm_policy inconsistent. * Generic/driver-specific logic boundary isn't clear. Currently, libahci has to mangle stuff which libata EH proper should be handling. This makes the implementation unnecessarily complex and fragile. * Tied to ALPM. Doesn't consider DIPM only cases and doesn't check whether the device allows HIPM. * Error handling isn't implemented. Given the extent of mismatch with the rest of libata, I don't think trying to fix it piecewise makes much sense. This patch reimplements LPM support. * The new implementation is per-link. The target policy is still port-wide (ap->target_lpm_policy) but all the mechanisms and states are per-link and integrate well with the rest of link abstraction and can work with slave and PMP links. * Core EH has proper control of LPM state. LPM state is reconfigured when and only when reconfiguration is necessary. It makes sure that LPM state is reset when probing for new device on the link. Controller agnostic logic is now implemented in libata EH proper and driver implementation only has to deal with controller specifics. * Proper error handling. LPM config failure is attributed to the device on the link and LPM is disabled for the link if it fails repeatedly. * ops->enable/disable_pm() are replaced with single ops->set_lpm() which takes @policy and @hints. This simplifies driver specific implementation. Signed-off-by: Tejun Heo Signed-off-by: Jeff Garzik --- drivers/ata/ahci.c | 3 - drivers/ata/ahci.h | 1 - drivers/ata/ahci_platform.c | 3 - drivers/ata/libahci.c | 158 +++++++++------------------------- drivers/ata/libata-core.c | 201 +------------------------------------------- drivers/ata/libata-eh.c | 164 +++++++++++++++++++++++++++++++----- drivers/ata/libata-scsi.c | 11 ++- drivers/ata/libata.h | 4 - include/linux/libata.h | 17 ++-- 9 files changed, 206 insertions(+), 356 deletions(-) (limited to 'include/linux/libata.h') diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index 68f7b4216501..328826381a2d 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -1208,9 +1208,6 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) ata_port_pbar_desc(ap, AHCI_PCI_BAR, 0x100 + ap->port_no * 0x80, "port"); - /* set initial link pm policy */ - ap->lpm_policy = ATA_LPM_UNKNOWN; - /* set enclosure management message type */ if (ap->flags & ATA_FLAG_EM) ap->em_message_type = hpriv->em_msg_type; diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h index 93867d3f8dd3..1a2aacfdebc5 100644 --- a/drivers/ata/ahci.h +++ b/drivers/ata/ahci.h @@ -201,7 +201,6 @@ enum { AHCI_HFLAG_MV_PATA = (1 << 4), /* PATA port */ AHCI_HFLAG_NO_MSI = (1 << 5), /* no PCI MSI */ AHCI_HFLAG_NO_PMP = (1 << 6), /* no PMP */ - AHCI_HFLAG_NO_HOTPLUG = (1 << 7), /* ignore PxSERR.DIAG.N */ AHCI_HFLAG_SECT255 = (1 << 8), /* max 255 sectors */ AHCI_HFLAG_YES_NCQ = (1 << 9), /* force NCQ cap on */ AHCI_HFLAG_NO_SUSPEND = (1 << 10), /* don't suspend */ diff --git a/drivers/ata/ahci_platform.c b/drivers/ata/ahci_platform.c index 07556853c0d6..6fef1fa75c54 100644 --- a/drivers/ata/ahci_platform.c +++ b/drivers/ata/ahci_platform.c @@ -129,9 +129,6 @@ static int __init ahci_probe(struct platform_device *pdev) ata_port_desc(ap, "mmio %pR", mem); ata_port_desc(ap, "port 0x%x", 0x100 + ap->port_no * 0x80); - /* set initial link pm policy */ - ap->lpm_policy = ATA_LPM_UNKNOWN; - /* set enclosure management message type */ if (ap->flags & ATA_FLAG_EM) ap->em_message_type = hpriv->em_msg_type; diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index ed7803f2b4f1..437f92597788 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -56,8 +56,8 @@ MODULE_PARM_DESC(skip_host_reset, "skip global host reset (0=don't skip, 1=skip) module_param_named(ignore_sss, ahci_ignore_sss, int, 0444); MODULE_PARM_DESC(ignore_sss, "Ignore staggered spinup flag (0=don't ignore, 1=ignore)"); -static int ahci_enable_alpm(struct ata_port *ap, enum ata_lpm_policy policy); -static void ahci_disable_alpm(struct ata_port *ap); +static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, + unsigned hints); static ssize_t ahci_led_show(struct ata_port *ap, char *buf); static ssize_t ahci_led_store(struct ata_port *ap, const char *buf, size_t size); @@ -163,8 +163,7 @@ struct ata_port_operations ahci_ops = { .pmp_attach = ahci_pmp_attach, .pmp_detach = ahci_pmp_detach, - .enable_pm = ahci_enable_alpm, - .disable_pm = ahci_disable_alpm, + .set_lpm = ahci_set_lpm, .em_show = ahci_led_show, .em_store = ahci_led_store, .sw_activity_show = ahci_activity_show, @@ -641,126 +640,56 @@ static void ahci_power_up(struct ata_port *ap) writel(cmd | PORT_CMD_ICC_ACTIVE, port_mmio + PORT_CMD); } -static void ahci_disable_alpm(struct ata_port *ap) +static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, + unsigned int hints) { + struct ata_port *ap = link->ap; struct ahci_host_priv *hpriv = ap->host->private_data; - void __iomem *port_mmio = ahci_port_base(ap); - u32 cmd; struct ahci_port_priv *pp = ap->private_data; - - /* LPM bits should be disabled by libata-core */ - /* get the existing command bits */ - cmd = readl(port_mmio + PORT_CMD); - - /* disable ALPM and ASP */ - cmd &= ~PORT_CMD_ASP; - cmd &= ~PORT_CMD_ALPE; - - /* force the interface back to active */ - cmd |= PORT_CMD_ICC_ACTIVE; - - /* write out new cmd value */ - writel(cmd, port_mmio + PORT_CMD); - cmd = readl(port_mmio + PORT_CMD); - - /* wait 10ms to be sure we've come out of any low power state */ - msleep(10); - - /* clear out any PhyRdy stuff from interrupt status */ - writel(PORT_IRQ_PHYRDY, port_mmio + PORT_IRQ_STAT); - - /* go ahead and clean out PhyRdy Change from Serror too */ - ahci_scr_write(&ap->link, SCR_ERROR, ((1 << 16) | (1 << 18))); - - /* - * Clear flag to indicate that we should ignore all PhyRdy - * state changes - */ - hpriv->flags &= ~AHCI_HFLAG_NO_HOTPLUG; - - /* - * Enable interrupts on Phy Ready. - */ - pp->intr_mask |= PORT_IRQ_PHYRDY; - writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); - - /* - * don't change the link pm policy - we can be called - * just to turn of link pm temporarily - */ -} - -static int ahci_enable_alpm(struct ata_port *ap, enum ata_lpm_policy policy) -{ - struct ahci_host_priv *hpriv = ap->host->private_data; void __iomem *port_mmio = ahci_port_base(ap); - u32 cmd; - struct ahci_port_priv *pp = ap->private_data; - u32 asp; - - /* Make sure the host is capable of link power management */ - if (!(hpriv->cap & HOST_CAP_ALPM)) - return -EINVAL; - switch (policy) { - case ATA_LPM_MAX_POWER: - case ATA_LPM_UNKNOWN: + if (policy != ATA_LPM_MAX_POWER) { /* - * if we came here with ATA_LPM_UNKNOWN, - * it just means this is the first time we - * have tried to enable - default to max performance, - * and let the user go to lower power modes on request. + * Disable interrupts on Phy Ready. This keeps us from + * getting woken up due to spurious phy ready + * interrupts. */ - ahci_disable_alpm(ap); - return 0; - case ATA_LPM_MIN_POWER: - /* configure HBA to enter SLUMBER */ - asp = PORT_CMD_ASP; - break; - case ATA_LPM_MED_POWER: - /* configure HBA to enter PARTIAL */ - asp = 0; - break; - default: - return -EINVAL; + pp->intr_mask &= ~PORT_IRQ_PHYRDY; + writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); + + sata_link_scr_lpm(link, policy, false); } - /* - * Disable interrupts on Phy Ready. This keeps us from - * getting woken up due to spurious phy ready interrupts - * TBD - Hot plug should be done via polling now, is - * that even supported? - */ - pp->intr_mask &= ~PORT_IRQ_PHYRDY; - writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); + if (hpriv->cap & HOST_CAP_ALPM) { + u32 cmd = readl(port_mmio + PORT_CMD); - /* - * Set a flag to indicate that we should ignore all PhyRdy - * state changes since these can happen now whenever we - * change link state - */ - hpriv->flags |= AHCI_HFLAG_NO_HOTPLUG; + if (policy == ATA_LPM_MAX_POWER || !(hints & ATA_LPM_HIPM)) { + cmd &= ~(PORT_CMD_ASP | PORT_CMD_ALPE); + cmd |= PORT_CMD_ICC_ACTIVE; - /* get the existing command bits */ - cmd = readl(port_mmio + PORT_CMD); + writel(cmd, port_mmio + PORT_CMD); + readl(port_mmio + PORT_CMD); - /* - * Set ASP based on Policy - */ - cmd |= asp; + /* wait 10ms to be sure we've come out of LPM state */ + msleep(10); + } else { + cmd |= PORT_CMD_ALPE; + if (policy == ATA_LPM_MIN_POWER) + cmd |= PORT_CMD_ASP; - /* - * Setting this bit will instruct the HBA to aggressively - * enter a lower power link state when it's appropriate and - * based on the value set above for ASP - */ - cmd |= PORT_CMD_ALPE; + /* write out new cmd value */ + writel(cmd, port_mmio + PORT_CMD); + } + } - /* write out new cmd value */ - writel(cmd, port_mmio + PORT_CMD); - cmd = readl(port_mmio + PORT_CMD); + if (policy == ATA_LPM_MAX_POWER) { + sata_link_scr_lpm(link, policy, false); + + /* turn PHYRDY IRQ back on */ + pp->intr_mask |= PORT_IRQ_PHYRDY; + writel(pp->intr_mask, port_mmio + PORT_IRQ_MASK); + } - /* LPM bits should be set by libata-core */ return 0; } @@ -1658,15 +1587,10 @@ static void ahci_port_intr(struct ata_port *ap) if (unlikely(resetting)) status &= ~PORT_IRQ_BAD_PMP; - /* If we are getting PhyRdy, this is - * just a power state change, we should - * clear out this, plus the PhyRdy/Comm - * Wake bits from Serror - */ - if ((hpriv->flags & AHCI_HFLAG_NO_HOTPLUG) && - (status & PORT_IRQ_PHYRDY)) { + /* if LPM is enabled, PHYRDY doesn't mean anything */ + if (ap->link.lpm_policy > ATA_LPM_MAX_POWER) { status &= ~PORT_IRQ_PHYRDY; - ahci_scr_write(&ap->link, SCR_ERROR, ((1 << 16) | (1 << 18))); + ahci_scr_write(&ap->link, SCR_ERROR, SERR_PHYRDY_CHG); } if (unlikely(status & PORT_IRQ_ERROR)) { diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index b8024451234c..7c5538b9fa3b 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -1028,182 +1028,6 @@ const char *sata_spd_string(unsigned int spd) return spd_str[spd - 1]; } -static int ata_dev_set_dipm(struct ata_device *dev, enum ata_lpm_policy policy) -{ - struct ata_link *link = dev->link; - struct ata_port *ap = link->ap; - u32 scontrol; - unsigned int err_mask; - int rc; - - /* - * disallow DIPM for drivers which haven't set - * ATA_FLAG_LPM. This is because when DIPM is enabled, - * phy ready will be set in the interrupt status on - * state changes, which will cause some drivers to - * think there are errors - additionally drivers will - * need to disable hot plug. - */ - if (!(ap->flags & ATA_FLAG_LPM) || !ata_dev_enabled(dev)) { - ap->lpm_policy = ATA_LPM_UNKNOWN; - return -EINVAL; - } - - /* - * For DIPM, we will only enable it for the - * min_power setting. - * - * Why? Because Disks are too stupid to know that - * If the host rejects a request to go to SLUMBER - * they should retry at PARTIAL, and instead it - * just would give up. So, for medium_power to - * work at all, we need to only allow HIPM. - */ - rc = sata_scr_read(link, SCR_CONTROL, &scontrol); - if (rc) - return rc; - - switch (policy) { - case ATA_LPM_MIN_POWER: - /* no restrictions on LPM transitions */ - scontrol &= ~(0x3 << 8); - rc = sata_scr_write(link, SCR_CONTROL, scontrol); - if (rc) - return rc; - - /* enable DIPM */ - if (dev->flags & ATA_DFLAG_DIPM) - err_mask = ata_dev_set_feature(dev, - SETFEATURES_SATA_ENABLE, SATA_DIPM); - break; - case ATA_LPM_MED_POWER: - /* allow LPM to PARTIAL */ - scontrol &= ~(0x1 << 8); - scontrol |= (0x2 << 8); - rc = sata_scr_write(link, SCR_CONTROL, scontrol); - if (rc) - return rc; - - /* - * we don't have to disable DIPM since LPM flags - * disallow transitions to SLUMBER, which effectively - * disable DIPM if it does not support PARTIAL - */ - break; - case ATA_LPM_UNKNOWN: - case ATA_LPM_MAX_POWER: - /* disable all LPM transitions */ - scontrol |= (0x3 << 8); - rc = sata_scr_write(link, SCR_CONTROL, scontrol); - if (rc) - return rc; - - /* - * we don't have to disable DIPM since LPM flags - * disallow all transitions which effectively - * disable DIPM anyway. - */ - break; - } - - /* FIXME: handle SET FEATURES failure */ - (void) err_mask; - - return 0; -} - -/** - * ata_dev_enable_pm - enable SATA interface power management - * @dev: device to enable power management - * @policy: the link power management policy - * - * Enable SATA Interface power management. This will enable - * Device Interface Power Management (DIPM) for min_power - * policy, and then call driver specific callbacks for - * enabling Host Initiated Power management. - * - * Locking: Caller. - * Returns: -EINVAL if LPM is not supported, 0 otherwise. - */ -void ata_dev_enable_pm(struct ata_device *dev, enum ata_lpm_policy policy) -{ - int rc = 0; - struct ata_port *ap = dev->link->ap; - - /* set HIPM first, then DIPM */ - if (ap->ops->enable_pm) - rc = ap->ops->enable_pm(ap, policy); - if (rc) - goto enable_pm_out; - rc = ata_dev_set_dipm(dev, policy); - -enable_pm_out: - if (rc) - ap->lpm_policy = ATA_LPM_MAX_POWER; - else - ap->lpm_policy = policy; - return /* rc */; /* hopefully we can use 'rc' eventually */ -} - -#ifdef CONFIG_PM -/** - * ata_dev_disable_pm - disable SATA interface power management - * @dev: device to disable power management - * - * Disable SATA Interface power management. This will disable - * Device Interface Power Management (DIPM) without changing - * policy, call driver specific callbacks for disabling Host - * Initiated Power management. - * - * Locking: Caller. - * Returns: void - */ -static void ata_dev_disable_pm(struct ata_device *dev) -{ - struct ata_port *ap = dev->link->ap; - - ata_dev_set_dipm(dev, ATA_LPM_MAX_POWER); - if (ap->ops->disable_pm) - ap->ops->disable_pm(ap); -} -#endif /* CONFIG_PM */ - -void ata_lpm_schedule(struct ata_port *ap, enum ata_lpm_policy policy) -{ - ap->lpm_policy = policy; - ap->link.eh_info.action |= ATA_EH_LPM; - ap->link.eh_info.flags |= ATA_EHI_NO_AUTOPSY; - ata_port_schedule_eh(ap); -} - -#ifdef CONFIG_PM -static void ata_lpm_enable(struct ata_host *host) -{ - struct ata_link *link; - struct ata_port *ap; - struct ata_device *dev; - int i; - - for (i = 0; i < host->n_ports; i++) { - ap = host->ports[i]; - ata_for_each_link(link, ap, EDGE) { - ata_for_each_dev(dev, link, ALL) - ata_dev_disable_pm(dev); - } - } -} - -static void ata_lpm_disable(struct ata_host *host) -{ - int i; - - for (i = 0; i < host->n_ports; i++) { - struct ata_port *ap = host->ports[i]; - ata_lpm_schedule(ap, ap->lpm_policy); - } -} -#endif /* CONFIG_PM */ - /** * ata_dev_classify - determine device type based on ATA-spec signature * @tf: ATA taskfile register set for device to be identified @@ -2562,13 +2386,6 @@ int ata_dev_configure(struct ata_device *dev) if (dev->flags & ATA_DFLAG_LBA48) dev->max_sectors = ATA_MAX_SECTORS_LBA48; - if (!(dev->horkage & ATA_HORKAGE_LPM)) { - if (ata_id_has_hipm(dev->id)) - dev->flags |= ATA_DFLAG_HIPM; - if (ata_id_has_dipm(dev->id)) - dev->flags |= ATA_DFLAG_DIPM; - } - /* Limit PATA drive on SATA cable bridge transfers to udma5, 200 sectors */ if (ata_dev_knobble(dev)) { @@ -2589,13 +2406,6 @@ int ata_dev_configure(struct ata_device *dev) dev->max_sectors = min_t(unsigned int, ATA_MAX_SECTORS_128, dev->max_sectors); - if (ata_dev_blacklisted(dev) & ATA_HORKAGE_LPM) { - dev->horkage |= ATA_HORKAGE_LPM; - - /* reset link pm_policy for this port to no pm */ - ap->lpm_policy = ATA_LPM_MAX_POWER; - } - if (ap->ops->dev_config) ap->ops->dev_config(dev); @@ -5494,12 +5304,6 @@ int ata_host_suspend(struct ata_host *host, pm_message_t mesg) unsigned int ehi_flags = ATA_EHI_QUIET; int rc; - /* - * disable link pm on all ports before requesting - * any pm activity - */ - ata_lpm_enable(host); - /* * On some hardware, device fails to respond after spun down * for suspend. As the device won't be used before being @@ -5533,9 +5337,6 @@ void ata_host_resume(struct ata_host *host) ata_host_request_pm(host, PMSG_ON, ATA_EH_RESET, ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET, 0); host->dev->power.power_state = PMSG_ON; - - /* reenable link pm */ - ata_lpm_disable(host); } #endif @@ -6096,7 +5897,7 @@ static void async_port_probe(void *data, async_cookie_t cookie) spin_lock_irqsave(ap->lock, flags); ehi->probe_mask |= ATA_ALL_DEVICES; - ehi->action |= ATA_EH_RESET | ATA_EH_LPM; + ehi->action |= ATA_EH_RESET; ehi->flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET; ap->pflags &= ~ATA_PFLAG_INITIALIZING; diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 96aa75ddf87f..a645cd3ab163 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -1580,9 +1580,9 @@ static void ata_eh_analyze_serror(struct ata_link *link) * host links. For disabled PMP links, only N bit is * considered as X bit is left at 1 for link plugging. */ - hotplug_mask = 0; - - if (!(link->flags & ATA_LFLAG_DISABLED) || ata_is_host_link(link)) + if (link->lpm_policy != ATA_LPM_MAX_POWER) + hotplug_mask = 0; /* hotplug doesn't work w/ LPM */ + else if (!(link->flags & ATA_LFLAG_DISABLED) || ata_is_host_link(link)) hotplug_mask = SERR_PHYRDY_CHG | SERR_DEV_XCHG; else hotplug_mask = SERR_PHYRDY_CHG; @@ -2784,8 +2784,9 @@ int ata_eh_reset(struct ata_link *link, int classify, ata_eh_done(link, NULL, ATA_EH_RESET); if (slave) ata_eh_done(slave, NULL, ATA_EH_RESET); - ehc->last_reset = jiffies; /* update to completion time */ + ehc->last_reset = jiffies; /* update to completion time */ ehc->i.action |= ATA_EH_REVALIDATE; + link->lpm_policy = ATA_LPM_UNKNOWN; /* reset LPM state */ rc = 0; out: @@ -3211,6 +3212,121 @@ static int ata_eh_maybe_retry_flush(struct ata_device *dev) return rc; } +/** + * ata_eh_set_lpm - configure SATA interface power management + * @link: link to configure power management + * @policy: the link power management policy + * @r_failed_dev: out parameter for failed device + * + * Enable SATA Interface power management. This will enable + * Device Interface Power Management (DIPM) for min_power + * policy, and then call driver specific callbacks for + * enabling Host Initiated Power management. + * + * LOCKING: + * EH context. + * + * RETURNS: + * 0 on success, -errno on failure. + */ +static int ata_eh_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, + struct ata_device **r_failed_dev) +{ + struct ata_port *ap = link->ap; + struct ata_eh_context *ehc = &link->eh_context; + struct ata_device *dev, *link_dev = NULL, *lpm_dev = NULL; + unsigned int hints = ATA_LPM_EMPTY | ATA_LPM_HIPM; + unsigned int err_mask; + int rc; + + /* if the link or host doesn't do LPM, noop */ + if ((link->flags & ATA_LFLAG_NO_LPM) || (ap && !ap->ops->set_lpm)) + return 0; + + /* + * DIPM is enabled only for MIN_POWER as some devices + * misbehave when the host NACKs transition to SLUMBER. Order + * device and link configurations such that the host always + * allows DIPM requests. + */ + ata_for_each_dev(dev, link, ENABLED) { + bool hipm = ata_id_has_hipm(dev->id); + bool dipm = ata_id_has_dipm(dev->id); + + /* find the first enabled and LPM enabled devices */ + if (!link_dev) + link_dev = dev; + + if (!lpm_dev && (hipm || dipm)) + lpm_dev = dev; + + hints &= ~ATA_LPM_EMPTY; + if (!hipm) + hints &= ~ATA_LPM_HIPM; + + /* disable DIPM before changing link config */ + if (policy != ATA_LPM_MIN_POWER && dipm) { + err_mask = ata_dev_set_feature(dev, + SETFEATURES_SATA_DISABLE, SATA_DIPM); + if (err_mask && err_mask != AC_ERR_DEV) { + ata_dev_printk(dev, KERN_WARNING, + "failed to disable DIPM, Emask 0x%x\n", + err_mask); + rc = -EIO; + goto fail; + } + } + } + + rc = ap->ops->set_lpm(link, policy, hints); + if (!rc && ap->slave_link) + rc = ap->ops->set_lpm(ap->slave_link, policy, hints); + + /* + * Attribute link config failure to the first (LPM) enabled + * device on the link. + */ + if (rc) { + if (rc == -EOPNOTSUPP) { + link->flags |= ATA_LFLAG_NO_LPM; + return 0; + } + dev = lpm_dev ? lpm_dev : link_dev; + goto fail; + } + + /* host config updated, enable DIPM if transitioning to MIN_POWER */ + ata_for_each_dev(dev, link, ENABLED) { + if (policy == ATA_LPM_MIN_POWER && ata_id_has_dipm(dev->id)) { + err_mask = ata_dev_set_feature(dev, + SETFEATURES_SATA_ENABLE, SATA_DIPM); + if (err_mask && err_mask != AC_ERR_DEV) { + ata_dev_printk(dev, KERN_WARNING, + "failed to enable DIPM, Emask 0x%x\n", + err_mask); + rc = -EIO; + goto fail; + } + } + } + + link->lpm_policy = policy; + if (ap && ap->slave_link) + ap->slave_link->lpm_policy = policy; + return 0; + +fail: + /* if no device or only one more chance is left, disable LPM */ + if (!dev || ehc->tries[dev->devno] <= 2) { + ata_link_printk(link, KERN_WARNING, + "disabling LPM on the link\n"); + link->flags |= ATA_LFLAG_NO_LPM; + } + if (r_failed_dev) + *r_failed_dev = dev; + return rc; +} + static int ata_link_nr_enabled(struct ata_link *link) { struct ata_device *dev; @@ -3295,6 +3411,10 @@ static int ata_eh_schedule_probe(struct ata_device *dev) ehc->saved_xfer_mode[dev->devno] = 0; ehc->saved_ncq_enabled &= ~(1 << dev->devno); + /* the link maybe in a deep sleep, wake it up */ + if (link->lpm_policy > ATA_LPM_MAX_POWER) + link->ap->ops->set_lpm(link, ATA_LPM_MAX_POWER, ATA_LPM_EMPTY); + /* Record and count probe trials on the ering. The specific * error mask used is irrelevant. Because a successful device * detection clears the ering, this count accumulates only if @@ -3396,8 +3516,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, { struct ata_link *link; struct ata_device *dev; - int nr_failed_devs; - int rc; + int rc, nr_fails; unsigned long flags, deadline; DPRINTK("ENTER\n"); @@ -3438,7 +3557,6 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, retry: rc = 0; - nr_failed_devs = 0; /* if UNLOADING, finish immediately */ if (ap->pflags & ATA_PFLAG_UNLOADING) @@ -3523,13 +3641,17 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, } /* the rest */ - ata_for_each_link(link, ap, EDGE) { + nr_fails = 0; + ata_for_each_link(link, ap, PMP_FIRST) { struct ata_eh_context *ehc = &link->eh_context; + if (sata_pmp_attached(ap) && ata_is_host_link(link)) + goto config_lpm; + /* revalidate existing devices and attach new ones */ rc = ata_eh_revalidate_and_attach(link, &dev); if (rc) - goto dev_fail; + goto rest_fail; /* if PMP got attached, return, pmp EH will take care of it */ if (link->device->class == ATA_DEV_PMP) { @@ -3541,7 +3663,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, if (ehc->i.flags & ATA_EHI_SETMODE) { rc = ata_set_mode(link, &dev); if (rc) - goto dev_fail; + goto rest_fail; ehc->i.flags &= ~ATA_EHI_SETMODE; } @@ -3554,7 +3676,7 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, continue; rc = atapi_eh_clear_ua(dev); if (rc) - goto dev_fail; + goto rest_fail; } } @@ -3564,21 +3686,25 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, continue; rc = ata_eh_maybe_retry_flush(dev); if (rc) - goto dev_fail; + goto rest_fail; } + config_lpm: /* configure link power saving */ - if (ehc->i.action & ATA_EH_LPM) - ata_for_each_dev(dev, link, ALL) - ata_dev_enable_pm(dev, ap->lpm_policy); + if (link->lpm_policy != ap->target_lpm_policy) { + rc = ata_eh_set_lpm(link, ap->target_lpm_policy, &dev); + if (rc) + goto rest_fail; + } /* this link is okay now */ ehc->i.flags = 0; continue; -dev_fail: - nr_failed_devs++; - ata_eh_handle_dev_fail(dev, rc); + rest_fail: + nr_fails++; + if (dev) + ata_eh_handle_dev_fail(dev, rc); if (ap->pflags & ATA_PFLAG_FROZEN) { /* PMP reset requires working host port. @@ -3590,7 +3716,7 @@ dev_fail: } } - if (nr_failed_devs) + if (nr_fails) goto retry; out: diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index aa56681f68db..56f6224fd6e6 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -117,6 +117,7 @@ static ssize_t ata_scsi_lpm_store(struct device *dev, struct Scsi_Host *shost = class_to_shost(dev); struct ata_port *ap = ata_shost_to_port(shost); enum ata_lpm_policy policy; + unsigned long flags; /* UNKNOWN is internal state, iterate from MAX_POWER */ for (policy = ATA_LPM_MAX_POWER; @@ -129,7 +130,11 @@ static ssize_t ata_scsi_lpm_store(struct device *dev, if (policy == ARRAY_SIZE(ata_lpm_policy_names)) return -EINVAL; - ata_lpm_schedule(ap, policy); + spin_lock_irqsave(ap->lock, flags); + ap->target_lpm_policy = policy; + ata_port_schedule_eh(ap); + spin_unlock_irqrestore(ap->lock, flags); + return count; } @@ -139,11 +144,11 @@ static ssize_t ata_scsi_lpm_show(struct device *dev, struct Scsi_Host *shost = class_to_shost(dev); struct ata_port *ap = ata_shost_to_port(shost); - if (ap->lpm_policy >= ARRAY_SIZE(ata_lpm_policy_names)) + if (ap->target_lpm_policy >= ARRAY_SIZE(ata_lpm_policy_names)) return -EINVAL; return snprintf(buf, PAGE_SIZE, "%s\n", - ata_lpm_policy_names[ap->lpm_policy]); + ata_lpm_policy_names[ap->target_lpm_policy]); } DEVICE_ATTR(link_power_management_policy, S_IRUGO | S_IWUSR, ata_scsi_lpm_show, ata_scsi_lpm_store); diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 65b6a73c3ac7..55a6f413a550 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -102,10 +102,6 @@ extern int sata_link_init_spd(struct ata_link *link); extern int ata_task_ioctl(struct scsi_device *scsidev, void __user *arg); extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg); extern struct ata_port *ata_port_alloc(struct ata_host *host); -extern void ata_dev_enable_pm(struct ata_device *dev, - enum ata_lpm_policy policy); -extern void ata_lpm_schedule(struct ata_port *ap, - enum ata_lpm_policy policy); extern const char *sata_spd_string(unsigned int spd); /* libata-acpi.c */ diff --git a/include/linux/libata.h b/include/linux/libata.h index 7770eeb21039..bc4ee218b185 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -172,6 +172,7 @@ enum { ATA_LFLAG_NO_RETRY = (1 << 5), /* don't retry this link */ ATA_LFLAG_DISABLED = (1 << 6), /* link is disabled */ ATA_LFLAG_SW_ACTIVITY = (1 << 7), /* keep activity stats */ + ATA_LFLAG_NO_LPM = (1 << 8), /* disable LPM on this link */ /* struct ata_port flags */ ATA_FLAG_SLAVE_POSS = (1 << 0), /* host supports slave dev */ @@ -324,12 +325,11 @@ enum { ATA_EH_HARDRESET = (1 << 2), /* meaningful only in ->prereset */ ATA_EH_RESET = ATA_EH_SOFTRESET | ATA_EH_HARDRESET, ATA_EH_ENABLE_LINK = (1 << 3), - ATA_EH_LPM = (1 << 4), /* link power management action */ ATA_EH_PARK = (1 << 5), /* unload heads and stop I/O */ ATA_EH_PERDEV_MASK = ATA_EH_REVALIDATE | ATA_EH_PARK, ATA_EH_ALL_ACTIONS = ATA_EH_REVALIDATE | ATA_EH_RESET | - ATA_EH_ENABLE_LINK | ATA_EH_LPM, + ATA_EH_ENABLE_LINK, /* ata_eh_info->flags */ ATA_EHI_HOTPLUGGED = (1 << 0), /* could have been hotplugged */ @@ -377,7 +377,6 @@ enum { ATA_HORKAGE_BROKEN_HPA = (1 << 4), /* Broken HPA */ ATA_HORKAGE_DISABLE = (1 << 5), /* Disable it */ ATA_HORKAGE_HPA_SIZE = (1 << 6), /* native size off by one */ - ATA_HORKAGE_LPM = (1 << 7), /* Link PM problems */ ATA_HORKAGE_IVB = (1 << 8), /* cbl det validity bit bugs */ ATA_HORKAGE_STUCK_ERR = (1 << 9), /* stuck ERR on next PACKET */ ATA_HORKAGE_BRIDGE_OK = (1 << 10), /* no bridge limits */ @@ -475,6 +474,11 @@ enum ata_lpm_policy { ATA_LPM_MIN_POWER, }; +enum ata_lpm_hints { + ATA_LPM_EMPTY = (1 << 0), /* port empty/probing */ + ATA_LPM_HIPM = (1 << 1), /* may use HIPM */ +}; + /* forward declarations */ struct scsi_device; struct ata_port_operations; @@ -702,6 +706,7 @@ struct ata_link { unsigned int hw_sata_spd_limit; unsigned int sata_spd_limit; unsigned int sata_spd; /* current SATA PHY speed */ + enum ata_lpm_policy lpm_policy; /* record runtime error info, protected by host_set lock */ struct ata_eh_info eh_info; @@ -773,7 +778,7 @@ struct ata_port { pm_message_t pm_mesg; int *pm_result; - enum ata_lpm_policy lpm_policy; + enum ata_lpm_policy target_lpm_policy; struct timer_list fastdrain_timer; unsigned long fastdrain_cnt; @@ -839,8 +844,8 @@ struct ata_port_operations { int (*scr_write)(struct ata_link *link, unsigned int sc_reg, u32 val); void (*pmp_attach)(struct ata_port *ap); void (*pmp_detach)(struct ata_port *ap); - int (*enable_pm)(struct ata_port *ap, enum ata_lpm_policy policy); - void (*disable_pm)(struct ata_port *ap); + int (*set_lpm)(struct ata_link *link, enum ata_lpm_policy policy, + unsigned hints); /* * Start, stop, suspend and resume -- cgit v1.2.3 From 97750cebb3000a9cc08f8ce8dc8c7143be7d7201 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 6 Sep 2010 17:56:29 +0200 Subject: libata: add @ap to ata_wait_register() and introduce ata_msleep() Add optional @ap argument to ata_wait_register() and replace msleep() calls with ata_msleep() which take optional @ap in addition to the duration. These will be used to implement EH exclusion. This patch doesn't cause any behavior difference. Signed-off-by: Tejun Heo Signed-off-by: Jeff Garzik --- drivers/ata/libahci.c | 18 +++++++++--------- drivers/ata/libata-core.c | 21 ++++++++++++++------- drivers/ata/libata-eh.c | 2 +- drivers/ata/libata-sff.c | 12 ++++++------ drivers/ata/pata_bf54x.c | 4 ++-- drivers/ata/pata_samsung_cf.c | 2 +- drivers/ata/pata_scc.c | 4 ++-- drivers/ata/sata_fsl.c | 19 ++++++++++--------- drivers/ata/sata_inic162x.c | 2 +- drivers/ata/sata_sil24.c | 14 +++++++------- drivers/ata/sata_via.c | 2 +- include/linux/libata.h | 5 +++-- 12 files changed, 57 insertions(+), 48 deletions(-) (limited to 'include/linux/libata.h') diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c index 437f92597788..524dbe8be163 100644 --- a/drivers/ata/libahci.c +++ b/drivers/ata/libahci.c @@ -567,7 +567,7 @@ int ahci_stop_engine(struct ata_port *ap) writel(tmp, port_mmio + PORT_CMD); /* wait for engine to stop. This could be as long as 500 msec */ - tmp = ata_wait_register(port_mmio + PORT_CMD, + tmp = ata_wait_register(ap, port_mmio + PORT_CMD, PORT_CMD_LIST_ON, PORT_CMD_LIST_ON, 1, 500); if (tmp & PORT_CMD_LIST_ON) return -EIO; @@ -614,7 +614,7 @@ static int ahci_stop_fis_rx(struct ata_port *ap) writel(tmp, port_mmio + PORT_CMD); /* wait for completion, spec says 500ms, give it 1000 */ - tmp = ata_wait_register(port_mmio + PORT_CMD, PORT_CMD_FIS_ON, + tmp = ata_wait_register(ap, port_mmio + PORT_CMD, PORT_CMD_FIS_ON, PORT_CMD_FIS_ON, 10, 1000); if (tmp & PORT_CMD_FIS_ON) return -EBUSY; @@ -671,7 +671,7 @@ static int ahci_set_lpm(struct ata_link *link, enum ata_lpm_policy policy, readl(port_mmio + PORT_CMD); /* wait 10ms to be sure we've come out of LPM state */ - msleep(10); + ata_msleep(ap, 10); } else { cmd |= PORT_CMD_ALPE; if (policy == ATA_LPM_MIN_POWER) @@ -740,7 +740,7 @@ static void ahci_start_port(struct ata_port *ap) emp->led_state, 4); if (rc == -EBUSY) - msleep(1); + ata_msleep(ap, 1); else break; } @@ -799,7 +799,7 @@ int ahci_reset_controller(struct ata_host *host) * reset must complete within 1 second, or * the hardware should be considered fried. */ - tmp = ata_wait_register(mmio + HOST_CTL, HOST_RESET, + tmp = ata_wait_register(NULL, mmio + HOST_CTL, HOST_RESET, HOST_RESET, 10, 1000); if (tmp & HOST_RESET) { @@ -1179,7 +1179,7 @@ int ahci_kick_engine(struct ata_port *ap) writel(tmp, port_mmio + PORT_CMD); rc = 0; - tmp = ata_wait_register(port_mmio + PORT_CMD, + tmp = ata_wait_register(ap, port_mmio + PORT_CMD, PORT_CMD_CLO, PORT_CMD_CLO, 1, 500); if (tmp & PORT_CMD_CLO) rc = -EIO; @@ -1209,8 +1209,8 @@ static int ahci_exec_polled_cmd(struct ata_port *ap, int pmp, writel(1, port_mmio + PORT_CMD_ISSUE); if (timeout_msec) { - tmp = ata_wait_register(port_mmio + PORT_CMD_ISSUE, 0x1, 0x1, - 1, timeout_msec); + tmp = ata_wait_register(ap, port_mmio + PORT_CMD_ISSUE, + 0x1, 0x1, 1, timeout_msec); if (tmp & 0x1) { ahci_kick_engine(ap); return -EBUSY; @@ -1257,7 +1257,7 @@ int ahci_do_softreset(struct ata_link *link, unsigned int *class, } /* spec says at least 5us, but be generous and sleep for 1ms */ - msleep(1); + ata_msleep(ap, 1); /* issue the second D2H Register FIS */ tf.ctl &= ~ATA_SRST; diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 7c5538b9fa3b..42d9ce29f50d 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -3404,7 +3404,7 @@ int ata_wait_ready(struct ata_link *link, unsigned long deadline, warned = 1; } - msleep(50); + ata_msleep(link->ap, 50); } } @@ -3425,7 +3425,7 @@ int ata_wait_ready(struct ata_link *link, unsigned long deadline, int ata_wait_after_reset(struct ata_link *link, unsigned long deadline, int (*check_ready)(struct ata_link *link)) { - msleep(ATA_WAIT_AFTER_RESET); + ata_msleep(link->ap, ATA_WAIT_AFTER_RESET); return ata_wait_ready(link, deadline, check_ready); } @@ -3473,7 +3473,7 @@ int sata_link_debounce(struct ata_link *link, const unsigned long *params, last_jiffies = jiffies; while (1) { - msleep(interval); + ata_msleep(link->ap, interval); if ((rc = sata_scr_read(link, SCR_STATUS, &cur))) return rc; cur &= 0xf; @@ -3538,7 +3538,7 @@ int sata_link_resume(struct ata_link *link, const unsigned long *params, * immediately after resuming. Delay 200ms before * debouncing. */ - msleep(200); + ata_msleep(link->ap, 200); /* is SControl restored correctly? */ if ((rc = sata_scr_read(link, SCR_CONTROL, &scontrol))) @@ -3742,7 +3742,7 @@ int sata_link_hardreset(struct ata_link *link, const unsigned long *timing, /* Couldn't find anything in SATA I/II specs, but AHCI-1.1 * 10.4.2 says at least 1 ms. */ - msleep(1); + ata_msleep(link->ap, 1); /* bring link back */ rc = sata_link_resume(link, timing, deadline); @@ -6483,8 +6483,14 @@ int ata_ratelimit(void) return __ratelimit(&ratelimit); } +void ata_msleep(struct ata_port *ap, unsigned int msecs) +{ + msleep(msecs); +} + /** * ata_wait_register - wait until register value changes + * @ap: ATA port to wait register for, can be NULL * @reg: IO-mapped register * @mask: Mask to apply to read register value * @val: Wait condition @@ -6506,7 +6512,7 @@ int ata_ratelimit(void) * RETURNS: * The final register value. */ -u32 ata_wait_register(void __iomem *reg, u32 mask, u32 val, +u32 ata_wait_register(struct ata_port *ap, void __iomem *reg, u32 mask, u32 val, unsigned long interval, unsigned long timeout) { unsigned long deadline; @@ -6521,7 +6527,7 @@ u32 ata_wait_register(void __iomem *reg, u32 mask, u32 val, deadline = ata_deadline(jiffies, timeout); while ((tmp & mask) == val && time_before(jiffies, deadline)) { - msleep(interval); + ata_msleep(ap, interval); tmp = ioread32(reg); } @@ -6605,6 +6611,7 @@ EXPORT_SYMBOL_GPL(ata_std_postreset); EXPORT_SYMBOL_GPL(ata_dev_classify); EXPORT_SYMBOL_GPL(ata_dev_pair); EXPORT_SYMBOL_GPL(ata_ratelimit); +EXPORT_SYMBOL_GPL(ata_msleep); EXPORT_SYMBOL_GPL(ata_wait_register); EXPORT_SYMBOL_GPL(ata_scsi_queuecmd); EXPORT_SYMBOL_GPL(ata_scsi_slave_config); diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 06a4db1ec10e..6780f4d16e81 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -779,7 +779,7 @@ void ata_port_wait_eh(struct ata_port *ap) /* make sure SCSI EH is complete */ if (scsi_host_in_recovery(ap->scsi_host)) { - msleep(10); + ata_msleep(ap, 10); goto retry; } } diff --git a/drivers/ata/libata-sff.c b/drivers/ata/libata-sff.c index 193f242eec05..14d18bf81255 100644 --- a/drivers/ata/libata-sff.c +++ b/drivers/ata/libata-sff.c @@ -222,7 +222,7 @@ int ata_sff_busy_sleep(struct ata_port *ap, timeout = ata_deadline(timer_start, tmout_pat); while (status != 0xff && (status & ATA_BUSY) && time_before(jiffies, timeout)) { - msleep(50); + ata_msleep(ap, 50); status = ata_sff_busy_wait(ap, ATA_BUSY, 3); } @@ -234,7 +234,7 @@ int ata_sff_busy_sleep(struct ata_port *ap, timeout = ata_deadline(timer_start, tmout); while (status != 0xff && (status & ATA_BUSY) && time_before(jiffies, timeout)) { - msleep(50); + ata_msleep(ap, 50); status = ap->ops->sff_check_status(ap); } @@ -360,7 +360,7 @@ static void ata_dev_select(struct ata_port *ap, unsigned int device, if (wait) { if (can_sleep && ap->link.device[device].class == ATA_DEV_ATAPI) - msleep(150); + ata_msleep(ap, 150); ata_wait_idle(ap); } } @@ -1356,7 +1356,7 @@ fsm_start: */ status = ata_sff_busy_wait(ap, ATA_BUSY, 5); if (status & ATA_BUSY) { - msleep(2); + ata_msleep(ap, 2); status = ata_sff_busy_wait(ap, ATA_BUSY, 10); if (status & ATA_BUSY) { ata_sff_queue_pio_task(link, ATA_SHORT_PAUSE); @@ -1937,7 +1937,7 @@ int ata_sff_wait_after_reset(struct ata_link *link, unsigned int devmask, unsigned int dev1 = devmask & (1 << 1); int rc, ret = 0; - msleep(ATA_WAIT_AFTER_RESET); + ata_msleep(ap, ATA_WAIT_AFTER_RESET); /* always check readiness of the master device */ rc = ata_sff_wait_ready(link, deadline); @@ -1966,7 +1966,7 @@ int ata_sff_wait_after_reset(struct ata_link *link, unsigned int devmask, lbal = ioread8(ioaddr->lbal_addr); if ((nsect == 1) && (lbal == 1)) break; - msleep(50); /* give drive a breather */ + ata_msleep(ap, 50); /* give drive a breather */ } rc = ata_sff_wait_ready(link, deadline); diff --git a/drivers/ata/pata_bf54x.c b/drivers/ata/pata_bf54x.c index 9cae65de750e..e1423cd2531f 100644 --- a/drivers/ata/pata_bf54x.c +++ b/drivers/ata/pata_bf54x.c @@ -1046,7 +1046,7 @@ static void bfin_bus_post_reset(struct ata_port *ap, unsigned int devmask) dev1 = 0; break; } - msleep(50); /* give drive a breather */ + ata_msleep(ap, 50); /* give drive a breather */ } if (dev1) ata_sff_busy_sleep(ap, ATA_TMOUT_BOOT_QUICK, ATA_TMOUT_BOOT); @@ -1087,7 +1087,7 @@ static unsigned int bfin_bus_softreset(struct ata_port *ap, * * Old drivers/ide uses the 2mS rule and then waits for ready */ - msleep(150); + ata_msleep(ap, 150); /* Before we perform post reset processing we want to see if * the bus shows 0xFF because the odd clown forgets the D7 diff --git a/drivers/ata/pata_samsung_cf.c b/drivers/ata/pata_samsung_cf.c index 6f9cfb24b751..8a51d673e5b2 100644 --- a/drivers/ata/pata_samsung_cf.c +++ b/drivers/ata/pata_samsung_cf.c @@ -322,7 +322,7 @@ static int pata_s3c_wait_after_reset(struct ata_link *link, { int rc; - msleep(ATA_WAIT_AFTER_RESET); + ata_msleep(link->ap, ATA_WAIT_AFTER_RESET); /* always check readiness of the master device */ rc = ata_sff_wait_ready(link, deadline); diff --git a/drivers/ata/pata_scc.c b/drivers/ata/pata_scc.c index fe36966f7e34..093715c3273a 100644 --- a/drivers/ata/pata_scc.c +++ b/drivers/ata/pata_scc.c @@ -530,7 +530,7 @@ static int scc_wait_after_reset(struct ata_link *link, unsigned int devmask, * * Old drivers/ide uses the 2mS rule and then waits for ready. */ - msleep(150); + ata_msleep(ap, 150); /* always check readiness of the master device */ rc = ata_sff_wait_ready(link, deadline); @@ -559,7 +559,7 @@ static int scc_wait_after_reset(struct ata_link *link, unsigned int devmask, lbal = in_be32(ioaddr->lbal_addr); if ((nsect == 1) && (lbal == 1)) break; - msleep(50); /* give drive a breather */ + ata_msleep(ap, 50); /* give drive a breather */ } rc = ata_sff_wait_ready(link, deadline); diff --git a/drivers/ata/sata_fsl.c b/drivers/ata/sata_fsl.c index 1440dc0af242..b0214d00d50b 100644 --- a/drivers/ata/sata_fsl.c +++ b/drivers/ata/sata_fsl.c @@ -678,7 +678,7 @@ static void sata_fsl_port_stop(struct ata_port *ap) iowrite32(temp, hcr_base + HCONTROL); /* Poll for controller to go offline - should happen immediately */ - ata_wait_register(hcr_base + HSTATUS, ONLINE, ONLINE, 1, 1); + ata_wait_register(ap, hcr_base + HSTATUS, ONLINE, ONLINE, 1, 1); ap->private_data = NULL; dma_free_coherent(dev, SATA_FSL_PORT_PRIV_DMA_SZ, @@ -729,7 +729,8 @@ try_offline_again: iowrite32(temp, hcr_base + HCONTROL); /* Poll for controller to go offline */ - temp = ata_wait_register(hcr_base + HSTATUS, ONLINE, ONLINE, 1, 500); + temp = ata_wait_register(ap, hcr_base + HSTATUS, ONLINE, ONLINE, + 1, 500); if (temp & ONLINE) { ata_port_printk(ap, KERN_ERR, @@ -752,7 +753,7 @@ try_offline_again: /* * PHY reset should remain asserted for atleast 1ms */ - msleep(1); + ata_msleep(ap, 1); /* * Now, bring the host controller online again, this can take time @@ -766,7 +767,7 @@ try_offline_again: temp |= HCONTROL_PMP_ATTACHED; iowrite32(temp, hcr_base + HCONTROL); - temp = ata_wait_register(hcr_base + HSTATUS, ONLINE, 0, 1, 500); + temp = ata_wait_register(ap, hcr_base + HSTATUS, ONLINE, 0, 1, 500); if (!(temp & ONLINE)) { ata_port_printk(ap, KERN_ERR, @@ -784,7 +785,7 @@ try_offline_again: * presence */ - temp = ata_wait_register(hcr_base + HSTATUS, 0xFF, 0, 1, 500); + temp = ata_wait_register(ap, hcr_base + HSTATUS, 0xFF, 0, 1, 500); if ((!(temp & 0x10)) || ata_link_offline(link)) { ata_port_printk(ap, KERN_WARNING, "No Device OR PHYRDY change,Hstatus = 0x%x\n", @@ -797,7 +798,7 @@ try_offline_again: * Wait for the first D2H from device,i.e,signature update notification */ start_jiffies = jiffies; - temp = ata_wait_register(hcr_base + HSTATUS, 0xFF, 0x10, + temp = ata_wait_register(ap, hcr_base + HSTATUS, 0xFF, 0x10, 500, jiffies_to_msecs(deadline - start_jiffies)); if ((temp & 0xFF) != 0x18) { @@ -880,7 +881,7 @@ static int sata_fsl_softreset(struct ata_link *link, unsigned int *class, iowrite32(pmp, CQPMP + hcr_base); iowrite32(1, CQ + hcr_base); - temp = ata_wait_register(CQ + hcr_base, 0x1, 0x1, 1, 5000); + temp = ata_wait_register(ap, CQ + hcr_base, 0x1, 0x1, 1, 5000); if (temp & 0x1) { ata_port_printk(ap, KERN_WARNING, "ATA_SRST issue failed\n"); @@ -896,7 +897,7 @@ static int sata_fsl_softreset(struct ata_link *link, unsigned int *class, goto err; } - msleep(1); + ata_msleep(ap, 1); /* * SATA device enters reset state after receving a Control register @@ -915,7 +916,7 @@ static int sata_fsl_softreset(struct ata_link *link, unsigned int *class, if (pmp != SATA_PMP_CTRL_PORT) iowrite32(pmp, CQPMP + hcr_base); iowrite32(1, CQ + hcr_base); - msleep(150); /* ?? */ + ata_msleep(ap, 150); /* ?? */ /* * The above command would have signalled an interrupt on command diff --git a/drivers/ata/sata_inic162x.c b/drivers/ata/sata_inic162x.c index a36149ebf4a2..83a44471b189 100644 --- a/drivers/ata/sata_inic162x.c +++ b/drivers/ata/sata_inic162x.c @@ -614,7 +614,7 @@ static int inic_hardreset(struct ata_link *link, unsigned int *class, writew(IDMA_CTL_RST_ATA, idma_ctl); readw(idma_ctl); /* flush */ - msleep(1); + ata_msleep(ap, 1); writew(0, idma_ctl); rc = sata_link_resume(link, timing, deadline); diff --git a/drivers/ata/sata_sil24.c b/drivers/ata/sata_sil24.c index be7726d7686d..af41c6fd1254 100644 --- a/drivers/ata/sata_sil24.c +++ b/drivers/ata/sata_sil24.c @@ -589,9 +589,9 @@ static int sil24_init_port(struct ata_port *ap) sil24_clear_pmp(ap); writel(PORT_CS_INIT, port + PORT_CTRL_STAT); - ata_wait_register(port + PORT_CTRL_STAT, + ata_wait_register(ap, port + PORT_CTRL_STAT, PORT_CS_INIT, PORT_CS_INIT, 10, 100); - tmp = ata_wait_register(port + PORT_CTRL_STAT, + tmp = ata_wait_register(ap, port + PORT_CTRL_STAT, PORT_CS_RDY, 0, 10, 100); if ((tmp & (PORT_CS_INIT | PORT_CS_RDY)) != PORT_CS_RDY) { @@ -631,7 +631,7 @@ static int sil24_exec_polled_cmd(struct ata_port *ap, int pmp, writel((u64)paddr >> 32, port + PORT_CMD_ACTIVATE + 4); irq_mask = (PORT_IRQ_COMPLETE | PORT_IRQ_ERROR) << PORT_IRQ_RAW_SHIFT; - irq_stat = ata_wait_register(port + PORT_IRQ_STAT, irq_mask, 0x0, + irq_stat = ata_wait_register(ap, port + PORT_IRQ_STAT, irq_mask, 0x0, 10, timeout_msec); writel(irq_mask, port + PORT_IRQ_STAT); /* clear IRQs */ @@ -719,9 +719,9 @@ static int sil24_hardreset(struct ata_link *link, unsigned int *class, "state, performing PORT_RST\n"); writel(PORT_CS_PORT_RST, port + PORT_CTRL_STAT); - msleep(10); + ata_msleep(ap, 10); writel(PORT_CS_PORT_RST, port + PORT_CTRL_CLR); - ata_wait_register(port + PORT_CTRL_STAT, PORT_CS_RDY, 0, + ata_wait_register(ap, port + PORT_CTRL_STAT, PORT_CS_RDY, 0, 10, 5000); /* restore port configuration */ @@ -740,7 +740,7 @@ static int sil24_hardreset(struct ata_link *link, unsigned int *class, tout_msec = 5000; writel(PORT_CS_DEV_RST, port + PORT_CTRL_STAT); - tmp = ata_wait_register(port + PORT_CTRL_STAT, + tmp = ata_wait_register(ap, port + PORT_CTRL_STAT, PORT_CS_DEV_RST, PORT_CS_DEV_RST, 10, tout_msec); @@ -1253,7 +1253,7 @@ static void sil24_init_controller(struct ata_host *host) tmp = readl(port + PORT_CTRL_STAT); if (tmp & PORT_CS_PORT_RST) { writel(PORT_CS_PORT_RST, port + PORT_CTRL_CLR); - tmp = ata_wait_register(port + PORT_CTRL_STAT, + tmp = ata_wait_register(NULL, port + PORT_CTRL_STAT, PORT_CS_PORT_RST, PORT_CS_PORT_RST, 10, 100); if (tmp & PORT_CS_PORT_RST) diff --git a/drivers/ata/sata_via.c b/drivers/ata/sata_via.c index 4730c42a5ee5..c21589986c69 100644 --- a/drivers/ata/sata_via.c +++ b/drivers/ata/sata_via.c @@ -349,7 +349,7 @@ static int vt6420_prereset(struct ata_link *link, unsigned long deadline) /* wait for phy to become ready, if necessary */ do { - msleep(200); + ata_msleep(link->ap, 200); svia_scr_read(link, SCR_STATUS, &sstatus); if ((sstatus & 0xf) != 1) break; diff --git a/include/linux/libata.h b/include/linux/libata.h index bc4ee218b185..2fbd22bd68ce 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -1004,8 +1004,9 @@ extern int ata_host_suspend(struct ata_host *host, pm_message_t mesg); extern void ata_host_resume(struct ata_host *host); #endif extern int ata_ratelimit(void); -extern u32 ata_wait_register(void __iomem *reg, u32 mask, u32 val, - unsigned long interval, unsigned long timeout); +extern void ata_msleep(struct ata_port *ap, unsigned int msecs); +extern u32 ata_wait_register(struct ata_port *ap, void __iomem *reg, u32 mask, + u32 val, unsigned long interval, unsigned long timeout); extern int atapi_cmd_type(u8 opcode); extern void ata_tf_to_fis(const struct ata_taskfile *tf, u8 pmp, int is_cmd, u8 *fis); -- cgit v1.2.3 From c0c362b60e259e3480a36ef70280d545818844f0 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 6 Sep 2010 17:57:14 +0200 Subject: libata: implement cross-port EH exclusion In libata, the non-EH code paths should always take and release ap->lock explicitly when accessing hardware or shared data structures. However, once EH is active, it's assumed that the port is owned by EH and EH methods don't explicitly take ap->lock unless race from irq handler or other code paths are expected. However, libata EH didn't guarantee exclusion among EHs for ports of the same host. IOW, multiple EHs may execute in parallel on multiple ports of the same controller. In many cases, especially in SATA, the ports are completely independent of each other and this doesn't cause problems; however, there are cases where different ports share the same resource, which lead to obscure timing related bugs such as the one fixed by commit 213373cf (ata_piix: fix locking around SIDPR access). This patch implements exclusion among EHs of the same host. When EH begins, it acquires per-host EH ownership by calling ata_eh_acquire(). When EH finishes, the ownership is released by calling ata_eh_release(). EH ownership is also released whenever the EH thread goes to sleep from ata_msleep() or explicitly and reacquired after waking up. This ensures that while EH is actively accessing the hardware, it has exclusive access to it while allowing EHs to interleave and progress in parallel as they hit waiting stages, which dominate the time spent in EH. This achieves cross-port EH exclusion without pervasive and fragile changes while still allowing parallel EH for the most part. This was first reported by yuanding02@gmail.com more than three years ago in the following bugzilla. :-) https://bugzilla.kernel.org/show_bug.cgi?id=8223 Signed-off-by: Tejun Heo Cc: Alan Cox Reported-by: yuanding02@gmail.com Signed-off-by: Jeff Garzik --- drivers/ata/libata-core.c | 30 ++++++++++++++++++++++++++++++ drivers/ata/libata-eh.c | 44 +++++++++++++++++++++++++++++++++++++++++++- drivers/ata/libata.h | 2 ++ include/linux/libata.h | 5 +++++ 4 files changed, 80 insertions(+), 1 deletion(-) (limited to 'include/linux/libata.h') diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 42d9ce29f50d..7f77c67d267c 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -1628,8 +1628,14 @@ unsigned ata_exec_internal_sg(struct ata_device *dev, } } + if (ap->ops->error_handler) + ata_eh_release(ap); + rc = wait_for_completion_timeout(&wait, msecs_to_jiffies(timeout)); + if (ap->ops->error_handler) + ata_eh_acquire(ap); + ata_sff_flush_pio_task(ap); if (!rc) { @@ -5570,6 +5576,7 @@ struct ata_host *ata_host_alloc(struct device *dev, int max_ports) dev_set_drvdata(dev, host); spin_lock_init(&host->lock); + mutex_init(&host->eh_mutex); host->dev = dev; host->n_ports = max_ports; @@ -5867,6 +5874,7 @@ void ata_host_init(struct ata_host *host, struct device *dev, unsigned long flags, struct ata_port_operations *ops) { spin_lock_init(&host->lock); + mutex_init(&host->eh_mutex); host->dev = dev; host->flags = flags; host->ops = ops; @@ -6483,9 +6491,31 @@ int ata_ratelimit(void) return __ratelimit(&ratelimit); } +/** + * ata_msleep - ATA EH owner aware msleep + * @ap: ATA port to attribute the sleep to + * @msecs: duration to sleep in milliseconds + * + * Sleeps @msecs. If the current task is owner of @ap's EH, the + * ownership is released before going to sleep and reacquired + * after the sleep is complete. IOW, other ports sharing the + * @ap->host will be allowed to own the EH while this task is + * sleeping. + * + * LOCKING: + * Might sleep. + */ void ata_msleep(struct ata_port *ap, unsigned int msecs) { + bool owns_eh = ap && ap->host->eh_owner == current; + + if (owns_eh) + ata_eh_release(ap); + msleep(msecs); + + if (owns_eh) + ata_eh_acquire(ap); } /** diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 6780f4d16e81..5e590504f3aa 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -462,6 +462,41 @@ static void ata_eh_clear_action(struct ata_link *link, struct ata_device *dev, } } +/** + * ata_eh_acquire - acquire EH ownership + * @ap: ATA port to acquire EH ownership for + * + * Acquire EH ownership for @ap. This is the basic exclusion + * mechanism for ports sharing a host. Only one port hanging off + * the same host can claim the ownership of EH. + * + * LOCKING: + * EH context. + */ +void ata_eh_acquire(struct ata_port *ap) +{ + mutex_lock(&ap->host->eh_mutex); + WARN_ON_ONCE(ap->host->eh_owner); + ap->host->eh_owner = current; +} + +/** + * ata_eh_release - release EH ownership + * @ap: ATA port to release EH ownership for + * + * Release EH ownership for @ap if the caller. The caller must + * have acquired EH ownership using ata_eh_acquire() previously. + * + * LOCKING: + * EH context. + */ +void ata_eh_release(struct ata_port *ap) +{ + WARN_ON_ONCE(ap->host->eh_owner != current); + ap->host->eh_owner = NULL; + mutex_unlock(&ap->host->eh_mutex); +} + /** * ata_scsi_timed_out - SCSI layer time out callback * @cmd: timed out SCSI command @@ -639,11 +674,13 @@ void ata_scsi_error(struct Scsi_Host *host) /* If we timed raced normal completion and there is nothing to recover nr_timedout == 0 why exactly are we doing error recovery ? */ - repeat: /* invoke error handler */ if (ap->ops->error_handler) { struct ata_link *link; + /* acquire EH ownership */ + ata_eh_acquire(ap); + repeat: /* kill fast drain timer */ del_timer_sync(&ap->fastdrain_timer); @@ -718,6 +755,7 @@ void ata_scsi_error(struct Scsi_Host *host) host->host_eh_scheduled = 0; spin_unlock_irqrestore(ap->lock, flags); + ata_eh_release(ap); } else { WARN_ON(ata_qc_from_tag(ap, ap->link.active_tag) == NULL); ap->ops->eng_timeout(ap); @@ -2818,8 +2856,10 @@ int ata_eh_reset(struct ata_link *link, int classify, "reset failed (errno=%d), retrying in %u secs\n", rc, DIV_ROUND_UP(jiffies_to_msecs(delta), 1000)); + ata_eh_release(ap); while (delta) delta = schedule_timeout_uninterruptible(delta); + ata_eh_acquire(ap); } if (try == max_tries - 1) { @@ -3635,8 +3675,10 @@ int ata_eh_recover(struct ata_port *ap, ata_prereset_fn_t prereset, if (time_before_eq(deadline, now)) break; + ata_eh_release(ap); deadline = wait_for_completion_timeout(&ap->park_req_pending, deadline - now); + ata_eh_acquire(ap); } while (deadline); ata_for_each_link(link, ap, EDGE) { ata_for_each_dev(dev, link, ALL) { diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index 7c070a4b1c08..a9be110dbf51 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -145,6 +145,8 @@ extern int ata_scsi_user_scan(struct Scsi_Host *shost, unsigned int channel, /* libata-eh.c */ extern unsigned long ata_internal_cmd_timeout(struct ata_device *dev, u8 cmd); extern void ata_internal_cmd_timed_out(struct ata_device *dev, u8 cmd); +extern void ata_eh_acquire(struct ata_port *ap); +extern void ata_eh_release(struct ata_port *ap); extern enum blk_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); diff --git a/include/linux/libata.h b/include/linux/libata.h index 2fbd22bd68ce..52112d39d71e 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -37,6 +37,7 @@ #include #include #include +#include /* * Define if arch has non-standard setup. This is a _PCI_ standard @@ -535,6 +536,10 @@ struct ata_host { void *private_data; struct ata_port_operations *ops; unsigned long flags; + + struct mutex eh_mutex; + struct task_struct *eh_owner; + #ifdef CONFIG_ATA_ACPI acpi_handle acpi_handle; #endif -- cgit v1.2.3 From b34e90429ce8a23546b6b927d4e151df4c113644 Mon Sep 17 00:00:00 2001 From: Richard Kennedy Date: Fri, 10 Sep 2010 12:19:43 +0100 Subject: libata: reorder ata_queued_cmd to remove alignment padding on 64 bit builds Reorder structure ata_queued_cmd to remove 8 bytes of alignment padding on 64 bit builds & therefore reduce the size of structure ata_port by 256 bytes. Overall this will have little impact, other than reducing the amount of memory that is cleared when allocating ata_ports. Signed-off-by: Richard Kennedy Signed-off-by: Jeff Garzik --- include/linux/libata.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include/linux/libata.h') diff --git a/include/linux/libata.h b/include/linux/libata.h index 52112d39d71e..15efec05df67 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -570,13 +570,13 @@ struct ata_queued_cmd { unsigned int extrabytes; unsigned int curbytes; - struct scatterlist *cursg; - unsigned int cursg_ofs; - struct scatterlist sgent; struct scatterlist *sg; + struct scatterlist *cursg; + unsigned int cursg_ofs; + unsigned int err_mask; struct ata_taskfile result_tf; ata_qc_cb_t complete_fn; -- cgit v1.2.3 From 89692c03226a066a017048cf7fbacbaa645f0e79 Mon Sep 17 00:00:00 2001 From: Andrea Gelmini Date: Sat, 16 Oct 2010 15:19:18 +0200 Subject: include/linux/libata.h: fix typo Signed-off-by: Andrea Gelmini Signed-off-by: Jeff Garzik --- include/linux/libata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux/libata.h') diff --git a/include/linux/libata.h b/include/linux/libata.h index 15efec05df67..15b77b8dc7e1 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -342,7 +342,7 @@ enum { ATA_EHI_DID_HARDRESET = (1 << 17), /* already soft-reset this port */ ATA_EHI_PRINTINFO = (1 << 18), /* print configuration info */ ATA_EHI_SETMODE = (1 << 19), /* configure transfer mode */ - ATA_EHI_POST_SETMODE = (1 << 20), /* revaildating after setmode */ + ATA_EHI_POST_SETMODE = (1 << 20), /* revalidating after setmode */ ATA_EHI_DID_RESET = ATA_EHI_DID_SOFTRESET | ATA_EHI_DID_HARDRESET, -- cgit v1.2.3