/*
* 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 <linux/slab.h>
#include <scsi/scsi_transport.h>
#include <linux/libata.h>
#include <linux/hdreg.h>
#include <linux/uaccess.h>
#include <linux/pm_runtime.h>
#include "libata.h"
#include "libata-transport.h"
#define ATA_PORT_ATTRS 3
#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_ZAC, "zac" },
{ 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);
ata_port_simple_attr(local_port_no, port_no, "%u\n", unsigned int);
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
*/
static 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->type = &ata_port_type;
dev->parent = get_device(parent);
dev->release = ata_tport_release;
dev_set_name(dev, "ata%d", ap->print_id);
transp