From patchwork Tue May 25 19:31:38 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gwendal Grignou X-Patchwork-Id: 53574 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id EFED0B7D19 for ; Wed, 26 May 2010 05:33:40 +1000 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755733Ab0EYTdj (ORCPT ); Tue, 25 May 2010 15:33:39 -0400 Received: from smtp-out.google.com ([74.125.121.35]:55343 "EHLO smtp-out.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754053Ab0EYTdi (ORCPT ); Tue, 25 May 2010 15:33:38 -0400 Received: from kpbe15.cbf.corp.google.com (kpbe15.cbf.corp.google.com [172.25.105.79]) by smtp-out.google.com with ESMTP id o4PJWBjE012171; Tue, 25 May 2010 12:32:12 -0700 DKIM-Signature: v=1; a=rsa-sha1; c=relaxed/relaxed; d=google.com; s=beta; t=1274815933; bh=dHnLWU5KTTRkJ94EVkeMYDrlkms=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References; b=sGCcJLF23ehoc+c463SB5FSXNpNtLBVhuMnHihZjYsdIgpZBfY1ABNQI0AEHZAS8s DBaC8xB6sqm7bGOjtfvfg== DomainKey-Signature: a=rsa-sha1; s=beta; d=google.com; c=nofws; q=dns; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; b=rzmNv4dNdadIcHouhnjYse2aYT8utbqUlrIabbT14cjsK+vv50WEgDpOcCalev1xQ yZuxUFRghfI0yuGBxEmUw== Received: from hippo2.mtv.corp.google.com (hippo2.mtv.corp.google.com [172.18.118.87]) by kpbe15.cbf.corp.google.com with ESMTP id o4PJW7HY016184; Tue, 25 May 2010 12:32:07 -0700 Received: by hippo2.mtv.corp.google.com (Postfix, from userid 60833) id 051805A167; Tue, 25 May 2010 12:32:07 -0700 (PDT) From: Gwendal Grignou To: jgarzik@pobox.com Cc: linux-ide@vger.kernel.org, Gwendal Grignou Subject: [PATCH] libata: Add libata transport class. Date: Tue, 25 May 2010 12:31:38 -0700 Message-Id: <1274815898-14439-2-git-send-email-gwendal@google.com> X-Mailer: git-send-email 1.7.0.1 In-Reply-To: <1274815898-14439-1-git-send-email-gwendal@google.com> References: <1274815898-14439-1-git-send-email-gwendal@google.com> Sender: linux-ide-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-ide@vger.kernel.org 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 --- Documentation/ABI/testing/sysfs-ata | 99 +++++ drivers/ata/Makefile | 2 +- drivers/ata/libata-core.c | 43 ++- 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, 982 insertions(+), 39 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 diff --git a/Documentation/ABI/testing/sysfs-ata b/Documentation/ABI/testing/sysfs-ata new file mode 100644 index 0000000..0a93215 --- /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 fc936d4..cd00fc7 100644 --- a/drivers/ata/Makefile +++ b/drivers/ata/Makefile @@ -86,7 +86,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 49cffb6..fd6dc46 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -67,7 +67,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 }; @@ -1015,7 +1015,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", @@ -5632,7 +5632,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; @@ -5706,7 +5707,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->flags = ATA_FLAG_DISABLED; @@ -6215,9 +6216,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); @@ -6258,6 +6268,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; + } /** @@ -6348,6 +6365,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); } @@ -6680,6 +6704,13 @@ static int __init ata_init(void) if (!ata_aux_wq) goto free_wq; + libata_transport_init(); + ata_scsi_transport_template = ata_attach_transport(); + if (ata_scsi_transport_template == NULL) { + destroy_workqueue(ata_wq); + return -ENOMEM; + } + printk(KERN_DEBUG "libata version " DRV_VERSION " loaded.\n"); return 0; @@ -6692,6 +6723,8 @@ free_force_tbl: static void __exit ata_exit(void) { + ata_release_transport(ata_scsi_transport_template); + libata_transport_exit(); kfree(ata_force_tbl); destroy_workqueue(ata_wq); destroy_workqueue(ata_aux_wq); diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 228740f..885005f 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 00305f4..96eba24 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, @@ -286,10 +287,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, @@ -301,6 +302,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++) { @@ -313,6 +321,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 0088cde..36d268b 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; @@ -3305,7 +3292,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; @@ -3588,8 +3575,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 0000000..a80860f --- /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 0000000..2820cf8 --- /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 823e630..4b94908 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -115,6 +115,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 @@ -150,6 +151,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); @@ -177,6 +181,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 b2f2003..fe28bf0 100644 --- a/include/linux/libata.h +++ b/include/linux/libata.h @@ -605,6 +605,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 */ @@ -691,6 +692,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 */ @@ -708,6 +710,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 */ @@ -750,6 +754,7 @@ struct ata_port { struct ata_port_stats stats; struct ata_host *host; struct device *dev; + struct device tdev; void *port_task_data; struct delayed_work port_task;