Message ID | 1274815898-14439-2-git-send-email-gwendal@google.com |
---|---|
State | Not Applicable |
Delegated to: | David Miller |
Headers | show |
On Tue, May 25, 2010 at 12:31 PM, Gwendal Grignou <gwendal@google.com> wrote: > This is a scheleton for libata transport class. Ping? Jeff, I thought you expressed an interest in this previously? I don't expect this to go into 2.6.34 but hoped it would make it into 2.6.35. thanks, grant > 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 <gwendal@google.com> > Reviewed-by: Grant Grundler <grundler@google.com> > --- > 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<gwendal@google.com> > +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 <linux/cdrom.h> > > #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 "<n/a>"; > } > > -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 <linux/libata.h> > #include <linux/slab.h> > #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 <asm/unaligned.h> > > #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 <linux/kernel.h> > +#include <linux/blkdev.h> > +#include <linux/spinlock.h> > +#include <scsi/scsi_transport.h> > +#include <linux/libata.h> > +#include <linux/hdreg.h> > +#include <linux/uaccess.h> > + > +#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;i<ATA_ID_WORDS;i++) { > + written += snprintf(buf+written, 20, "%04x%c", > + ata_dev->id[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;i<SATA_PMP_GSCR_DWORDS;i++) { > + written += snprintf(buf+written, 20, "%08x%c", > + ata_dev->gscr[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; > -- > 1.7.0.1 > > -- > To unsubscribe from this list: send the line "unsubscribe linux-ide" in > the body of a message to majordomo@vger.kernel.org > More majordomo info at http://vger.kernel.org/majordomo-info.html >
On 06/25/2010 11:02 PM, Grant Grundler wrote: > On Tue, May 25, 2010 at 12:31 PM, Gwendal Grignou<gwendal@google.com> wrote: >> This is a scheleton for libata transport class. > > Ping? > > Jeff, > I thought you expressed an interest in this previously? > > I don't expect this to go into 2.6.34 but hoped it would make it into 2.6.35. I actually wanted to wait until 2.6.35 release for this. Let's toss it into #upstream for 2.6.36, and see what comments percolate out. Jeff -- To unsubscribe from this list: send the line "unsubscribe linux-ide" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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<gwendal@google.com> +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 <linux/cdrom.h> #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 "<n/a>"; } -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 <linux/libata.h> #include <linux/slab.h> #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 <asm/unaligned.h> #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 <linux/kernel.h> +#include <linux/blkdev.h> +#include <linux/spinlock.h> +#include <scsi/scsi_transport.h> +#include <linux/libata.h> +#include <linux/hdreg.h> +#include <linux/uaccess.h> + +#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;i<ATA_ID_WORDS;i++) { + written += snprintf(buf+written, 20, "%04x%c", + ata_dev->id[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;i<SATA_PMP_GSCR_DWORDS;i++) { + written += snprintf(buf+written, 20, "%08x%c", + ata_dev->gscr[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;