libata/ahci: enclosure management support

Add Enclosure Management support to libata and ahci.

Signed-off-by:  Kristen Carlson Accardi <kristen.c.accardi@intel.com>
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
This commit is contained in:
Kristen Carlson Accardi 2008-06-03 10:33:55 -07:00 committed by Jeff Garzik
parent 87fbc5a060
commit 18f7ba4c2f
3 changed files with 419 additions and 2 deletions

View File

@ -56,6 +56,12 @@ MODULE_PARM_DESC(skip_host_reset, "skip global host reset (0=don't skip, 1=skip)
static int ahci_enable_alpm(struct ata_port *ap,
enum link_pm policy);
static void ahci_disable_alpm(struct ata_port *ap);
static ssize_t ahci_led_show(struct ata_port *ap, char *buf);
static ssize_t ahci_led_store(struct ata_port *ap, const char *buf,
size_t size);
static ssize_t ahci_transmit_led_message(struct ata_port *ap, u32 state,
ssize_t size);
#define MAX_SLOTS 8
enum {
AHCI_PCI_BAR = 5,
@ -98,6 +104,8 @@ enum {
HOST_IRQ_STAT = 0x08, /* interrupt status */
HOST_PORTS_IMPL = 0x0c, /* bitmap of implemented ports */
HOST_VERSION = 0x10, /* AHCI spec. version compliancy */
HOST_EM_LOC = 0x1c, /* Enclosure Management location */
HOST_EM_CTL = 0x20, /* Enclosure Management Control */
/* HOST_CTL bits */
HOST_RESET = (1 << 0), /* reset controller; self-clear */
@ -105,6 +113,7 @@ enum {
HOST_AHCI_EN = (1 << 31), /* AHCI enabled */
/* HOST_CAP bits */
HOST_CAP_EMS = (1 << 6), /* Enclosure Management support */
HOST_CAP_SSC = (1 << 14), /* Slumber capable */
HOST_CAP_PMP = (1 << 17), /* Port Multiplier support */
HOST_CAP_CLO = (1 << 24), /* Command List Override support */
@ -202,6 +211,11 @@ enum {
ATA_FLAG_IPM,
ICH_MAP = 0x90, /* ICH MAP register */
/* em_ctl bits */
EM_CTL_RST = (1 << 9), /* Reset */
EM_CTL_TM = (1 << 8), /* Transmit Message */
EM_CTL_ALHD = (1 << 26), /* Activity LED */
};
struct ahci_cmd_hdr {
@ -219,12 +233,21 @@ struct ahci_sg {
__le32 flags_size;
};
struct ahci_em_priv {
enum sw_activity blink_policy;
struct timer_list timer;
unsigned long saved_activity;
unsigned long activity;
unsigned long led_state;
};
struct ahci_host_priv {
unsigned int flags; /* AHCI_HFLAG_* */
u32 cap; /* cap to use */
u32 port_map; /* port map to use */
u32 saved_cap; /* saved initial cap */
u32 saved_port_map; /* saved initial port_map */
u32 em_loc; /* enclosure management location */
};
struct ahci_port_priv {
@ -240,6 +263,8 @@ struct ahci_port_priv {
unsigned int ncq_saw_dmas:1;
unsigned int ncq_saw_sdb:1;
u32 intr_mask; /* interrupts to enable */
struct ahci_em_priv em_priv[MAX_SLOTS];/* enclosure management info
* per PM slot */
};
static int ahci_scr_read(struct ata_port *ap, unsigned int sc_reg, u32 *val);
@ -277,9 +302,20 @@ static int ahci_port_suspend(struct ata_port *ap, pm_message_t mesg);
static int ahci_pci_device_suspend(struct pci_dev *pdev, pm_message_t mesg);
static int ahci_pci_device_resume(struct pci_dev *pdev);
#endif
static ssize_t ahci_activity_show(struct ata_device *dev, char *buf);
static ssize_t ahci_activity_store(struct ata_device *dev,
enum sw_activity val);
static void ahci_init_sw_activity(struct ata_link *link);
static struct device_attribute *ahci_shost_attrs[] = {
&dev_attr_link_power_management_policy,
&dev_attr_em_message_type,
&dev_attr_em_message,
NULL
};
static struct device_attribute *ahci_sdev_attrs[] = {
&dev_attr_sw_activity,
NULL
};
@ -289,6 +325,7 @@ static struct scsi_host_template ahci_sht = {
.sg_tablesize = AHCI_MAX_SG,
.dma_boundary = AHCI_DMA_BOUNDARY,
.shost_attrs = ahci_shost_attrs,
.sdev_attrs = ahci_sdev_attrs,
};
static struct ata_port_operations ahci_ops = {
@ -316,6 +353,10 @@ static struct ata_port_operations ahci_ops = {
.enable_pm = ahci_enable_alpm,
.disable_pm = ahci_disable_alpm,
.em_show = ahci_led_show,
.em_store = ahci_led_store,
.sw_activity_show = ahci_activity_show,
.sw_activity_store = ahci_activity_store,
#ifdef CONFIG_PM
.port_suspend = ahci_port_suspend,
.port_resume = ahci_port_resume,
@ -561,6 +602,11 @@ static struct pci_driver ahci_pci_driver = {
#endif
};
static int ahci_em_messages = 1;
module_param(ahci_em_messages, int, 0444);
/* add other LED protocol types when they become supported */
MODULE_PARM_DESC(ahci_em_messages,
"Set AHCI Enclosure Management Message type (0 = disabled, 1 = LED");
static inline int ahci_nr_ports(u32 cap)
{
@ -1031,11 +1077,28 @@ static void ahci_power_down(struct ata_port *ap)
static void ahci_start_port(struct ata_port *ap)
{
struct ahci_port_priv *pp = ap->private_data;
struct ata_link *link;
struct ahci_em_priv *emp;
/* enable FIS reception */
ahci_start_fis_rx(ap);
/* enable DMA */
ahci_start_engine(ap);
/* turn on LEDs */
if (ap->flags & ATA_FLAG_EM) {
ata_port_for_each_link(link, ap) {
emp = &pp->em_priv[link->pmp];
ahci_transmit_led_message(ap, emp->led_state, 4);
}
}
if (ap->flags & ATA_FLAG_SW_ACTIVITY)
ata_port_for_each_link(link, ap)
ahci_init_sw_activity(link);
}
static int ahci_deinit_port(struct ata_port *ap, const char **emsg)
@ -1116,6 +1179,230 @@ static int ahci_reset_controller(struct ata_host *host)
return 0;
}
static void ahci_sw_activity(struct ata_link *link)
{
struct ata_port *ap = link->ap;
struct ahci_port_priv *pp = ap->private_data;
struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
if (!(link->flags & ATA_LFLAG_SW_ACTIVITY))
return;
emp->activity++;
if (!timer_pending(&emp->timer))
mod_timer(&emp->timer, jiffies + msecs_to_jiffies(10));
}
static void ahci_sw_activity_blink(unsigned long arg)
{
struct ata_link *link = (struct ata_link *)arg;
struct ata_port *ap = link->ap;
struct ahci_port_priv *pp = ap->private_data;
struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
unsigned long led_message = emp->led_state;
u32 activity_led_state;
led_message &= 0xffff0000;
led_message |= ap->port_no | (link->pmp << 8);
/* check to see if we've had activity. If so,
* toggle state of LED and reset timer. If not,
* turn LED to desired idle state.
*/
if (emp->saved_activity != emp->activity) {
emp->saved_activity = emp->activity;
/* get the current LED state */
activity_led_state = led_message & 0x00010000;
if (activity_led_state)
activity_led_state = 0;
else
activity_led_state = 1;
/* clear old state */
led_message &= 0xfff8ffff;
/* toggle state */
led_message |= (activity_led_state << 16);
mod_timer(&emp->timer, jiffies + msecs_to_jiffies(100));
} else {
/* switch to idle */
led_message &= 0xfff8ffff;
if (emp->blink_policy == BLINK_OFF)
led_message |= (1 << 16);
}
ahci_transmit_led_message(ap, led_message, 4);
}
static void ahci_init_sw_activity(struct ata_link *link)
{
struct ata_port *ap = link->ap;
struct ahci_port_priv *pp = ap->private_data;
struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
/* init activity stats, setup timer */
emp->saved_activity = emp->activity = 0;
setup_timer(&emp->timer, ahci_sw_activity_blink, (unsigned long)link);
/* check our blink policy and set flag for link if it's enabled */
if (emp->blink_policy)
link->flags |= ATA_LFLAG_SW_ACTIVITY;
}
static int ahci_reset_em(struct ata_host *host)
{
void __iomem *mmio = host->iomap[AHCI_PCI_BAR];
u32 em_ctl;
em_ctl = readl(mmio + HOST_EM_CTL);
if ((em_ctl & EM_CTL_TM) || (em_ctl & EM_CTL_RST))
return -EINVAL;
writel(em_ctl | EM_CTL_RST, mmio + HOST_EM_CTL);
return 0;
}
static ssize_t ahci_transmit_led_message(struct ata_port *ap, u32 state,
ssize_t size)
{
struct ahci_host_priv *hpriv = ap->host->private_data;
struct ahci_port_priv *pp = ap->private_data;
void __iomem *mmio = ap->host->iomap[AHCI_PCI_BAR];
u32 em_ctl;
u32 message[] = {0, 0};
unsigned int flags;
int pmp;
struct ahci_em_priv *emp;
/* get the slot number from the message */
pmp = (state & 0x0000ff00) >> 8;
if (pmp < MAX_SLOTS)
emp = &pp->em_priv[pmp];
else
return -EINVAL;
spin_lock_irqsave(ap->lock, flags);
/*
* if we are still busy transmitting a previous message,
* do not allow
*/
em_ctl = readl(mmio + HOST_EM_CTL);
if (em_ctl & EM_CTL_TM) {
spin_unlock_irqrestore(ap->lock, flags);
return -EINVAL;
}
/*
* create message header - this is all zero except for
* the message size, which is 4 bytes.
*/
message[0] |= (4 << 8);
/* ignore 0:4 of byte zero, fill in port info yourself */
message[1] = ((state & 0xfffffff0) | ap->port_no);
/* write message to EM_LOC */
writel(message[0], mmio + hpriv->em_loc);
writel(message[1], mmio + hpriv->em_loc+4);
/* save off new led state for port/slot */
emp->led_state = message[1];
/*
* tell hardware to transmit the message
*/
writel(em_ctl | EM_CTL_TM, mmio + HOST_EM_CTL);
spin_unlock_irqrestore(ap->lock, flags);
return size;
}
static ssize_t ahci_led_show(struct ata_port *ap, char *buf)
{
struct ahci_port_priv *pp = ap->private_data;
struct ata_link *link;
struct ahci_em_priv *emp;
int rc = 0;
ata_port_for_each_link(link, ap) {
emp = &pp->em_priv[link->pmp];
rc += sprintf(buf, "%lx\n", emp->led_state);
}
return rc;
}
static ssize_t ahci_led_store(struct ata_port *ap, const char *buf,
size_t size)
{
int state;
int pmp;
struct ahci_port_priv *pp = ap->private_data;
struct ahci_em_priv *emp;
state = simple_strtoul(buf, NULL, 0);
/* get the slot number from the message */
pmp = (state & 0x0000ff00) >> 8;
if (pmp < MAX_SLOTS)
emp = &pp->em_priv[pmp];
else
return -EINVAL;
/* mask off the activity bits if we are in sw_activity
* mode, user should turn off sw_activity before setting
* activity led through em_message
*/
if (emp->blink_policy)
state &= 0xfff8ffff;
return ahci_transmit_led_message(ap, state, size);
}
static ssize_t ahci_activity_store(struct ata_device *dev, enum sw_activity val)
{
struct ata_link *link = dev->link;
struct ata_port *ap = link->ap;
struct ahci_port_priv *pp = ap->private_data;
struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
u32 port_led_state = emp->led_state;
/* save the desired Activity LED behavior */
if (val == OFF) {
/* clear LFLAG */
link->flags &= ~(ATA_LFLAG_SW_ACTIVITY);
/* set the LED to OFF */
port_led_state &= 0xfff80000;
port_led_state |= (ap->port_no | (link->pmp << 8));
ahci_transmit_led_message(ap, port_led_state, 4);
} else {
link->flags |= ATA_LFLAG_SW_ACTIVITY;
if (val == BLINK_OFF) {
/* set LED to ON for idle */
port_led_state &= 0xfff80000;
port_led_state |= (ap->port_no | (link->pmp << 8));
port_led_state |= 0x00010000; /* check this */
ahci_transmit_led_message(ap, port_led_state, 4);
}
}
emp->blink_policy = val;
return 0;
}
static ssize_t ahci_activity_show(struct ata_device *dev, char *buf)
{
struct ata_link *link = dev->link;
struct ata_port *ap = link->ap;
struct ahci_port_priv *pp = ap->private_data;
struct ahci_em_priv *emp = &pp->em_priv[link->pmp];
/* display the saved value of activity behavior for this
* disk.
*/
return sprintf(buf, "%d\n", emp->blink_policy);
}
static void ahci_port_init(struct pci_dev *pdev, struct ata_port *ap,
int port_no, void __iomem *mmio,
void __iomem *port_mmio)
@ -1848,6 +2135,8 @@ static unsigned int ahci_qc_issue(struct ata_queued_cmd *qc)
writel(1 << qc->tag, port_mmio + PORT_CMD_ISSUE);
readl(port_mmio + PORT_CMD_ISSUE); /* flush */
ahci_sw_activity(qc->dev->link);
return 0;
}
@ -2154,7 +2443,8 @@ static void ahci_print_info(struct ata_host *host)
dev_printk(KERN_INFO, &pdev->dev,
"flags: "
"%s%s%s%s%s%s%s"
"%s%s%s%s%s%s%s\n"
"%s%s%s%s%s%s%s"
"%s\n"
,
cap & (1 << 31) ? "64bit " : "",
@ -2171,7 +2461,8 @@ static void ahci_print_info(struct ata_host *host)
cap & (1 << 17) ? "pmp " : "",
cap & (1 << 15) ? "pio " : "",
cap & (1 << 14) ? "slum " : "",
cap & (1 << 13) ? "part " : ""
cap & (1 << 13) ? "part " : "",
cap & (1 << 6) ? "ems ": ""
);
}
@ -2291,6 +2582,24 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
if (hpriv->cap & HOST_CAP_PMP)
pi.flags |= ATA_FLAG_PMP;
if (ahci_em_messages && (hpriv->cap & HOST_CAP_EMS)) {
u8 messages;
void __iomem *mmio = pcim_iomap_table(pdev)[AHCI_PCI_BAR];
u32 em_loc = readl(mmio + HOST_EM_LOC);
u32 em_ctl = readl(mmio + HOST_EM_CTL);
messages = (em_ctl & 0x000f0000) >> 16;
/* we only support LED message type right now */
if ((messages & 0x01) && (ahci_em_messages == 1)) {
/* store em_loc */
hpriv->em_loc = ((em_loc >> 16) * 4);
pi.flags |= ATA_FLAG_EM;
if (!(em_ctl & EM_CTL_ALHD))
pi.flags |= ATA_FLAG_SW_ACTIVITY;
}
}
/* CAP.NP sometimes indicate the index of the last enabled
* port, at other times, that of the last possible port, so
* determining the maximum port number requires looking at
@ -2304,6 +2613,9 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
host->iomap = pcim_iomap_table(pdev);
host->private_data = hpriv;
if (pi.flags & ATA_FLAG_EM)
ahci_reset_em(host);
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
@ -2314,6 +2626,11 @@ static int ahci_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
/* set initial link pm policy */
ap->pm_policy = NOT_AVAILABLE;
/* set enclosure management message type */
if (ap->flags & ATA_FLAG_EM)
ap->em_message_type = ahci_em_messages;
/* disabled/not-implemented port */
if (!(hpriv->port_map & (1 << i)))
ap->ops = &ata_dummy_port_ops;

View File

@ -190,6 +190,85 @@ static void ata_scsi_set_sense(struct scsi_cmnd *cmd, u8 sk, u8 asc, u8 ascq)
scsi_build_sense_buffer(0, cmd->sense_buffer, sk, asc, ascq);
}
static ssize_t
ata_scsi_em_message_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct Scsi_Host *shost = class_to_shost(dev);
struct ata_port *ap = ata_shost_to_port(shost);
if (ap->ops->em_store && (ap->flags & ATA_FLAG_EM))
return ap->ops->em_store(ap, buf, count);
return -EINVAL;
}
static ssize_t
ata_scsi_em_message_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct Scsi_Host *shost = class_to_shost(dev);
struct ata_port *ap = ata_shost_to_port(shost);
if (ap->ops->em_show && (ap->flags & ATA_FLAG_EM))
return ap->ops->em_show(ap, buf);
return -EINVAL;
}
DEVICE_ATTR(em_message, S_IRUGO | S_IWUGO,
ata_scsi_em_message_show, ata_scsi_em_message_store);
EXPORT_SYMBOL_GPL(dev_attr_em_message);
static ssize_t
ata_scsi_em_message_type_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct Scsi_Host *shost = class_to_shost(dev);
struct ata_port *ap = ata_shost_to_port(shost);
return snprintf(buf, 23, "%d\n", ap->em_message_type);
}
DEVICE_ATTR(em_message_type, S_IRUGO,
ata_scsi_em_message_type_show, NULL);
EXPORT_SYMBOL_GPL(dev_attr_em_message_type);
static ssize_t
ata_scsi_activity_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct scsi_device *sdev = to_scsi_device(dev);
struct ata_port *ap = ata_shost_to_port(sdev->host);
struct ata_device *atadev = ata_scsi_find_dev(ap, sdev);
if (ap->ops->sw_activity_show && (ap->flags & ATA_FLAG_SW_ACTIVITY))
return ap->ops->sw_activity_show(atadev, buf);
return -EINVAL;
}
static ssize_t
ata_scsi_activity_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct scsi_device *sdev = to_scsi_device(dev);
struct ata_port *ap = ata_shost_to_port(sdev->host);
struct ata_device *atadev = ata_scsi_find_dev(ap, sdev);
enum sw_activity val;
int rc;
if (ap->ops->sw_activity_store && (ap->flags & ATA_FLAG_SW_ACTIVITY)) {
val = simple_strtoul(buf, NULL, 0);
switch (val) {
case OFF: case BLINK_ON: case BLINK_OFF:
rc = ap->ops->sw_activity_store(atadev, val);
if (!rc)
return count;
else
return rc;
}
}
return -EINVAL;
}
DEVICE_ATTR(sw_activity, S_IWUGO | S_IRUGO, ata_scsi_activity_show,
ata_scsi_activity_store);
EXPORT_SYMBOL_GPL(dev_attr_sw_activity);
static void ata_scsi_invalid_field(struct scsi_cmnd *cmd,
void (*done)(struct scsi_cmnd *))
{

View File

@ -169,6 +169,7 @@ enum {
ATA_LFLAG_ASSUME_CLASS = ATA_LFLAG_ASSUME_ATA | ATA_LFLAG_ASSUME_SEMB,
ATA_LFLAG_NO_RETRY = (1 << 5), /* don't retry this link */
ATA_LFLAG_DISABLED = (1 << 6), /* link is disabled */
ATA_LFLAG_SW_ACTIVITY = (1 << 7), /* keep activity stats */
/* struct ata_port flags */
ATA_FLAG_SLAVE_POSS = (1 << 0), /* host supports slave dev */
@ -191,6 +192,10 @@ enum {
ATA_FLAG_AN = (1 << 18), /* controller supports AN */
ATA_FLAG_PMP = (1 << 19), /* controller supports PMP */
ATA_FLAG_IPM = (1 << 20), /* driver can handle IPM */
ATA_FLAG_EM = (1 << 21), /* driver supports enclosure
* management */
ATA_FLAG_SW_ACTIVITY = (1 << 22), /* driver supports sw activity
* led */
/* The following flag belongs to ap->pflags but is kept in
* ap->flags because it's referenced in many LLDs and will be
@ -446,6 +451,15 @@ enum link_pm {
MEDIUM_POWER,
};
extern struct device_attribute dev_attr_link_power_management_policy;
extern struct device_attribute dev_attr_em_message_type;
extern struct device_attribute dev_attr_em_message;
extern struct device_attribute dev_attr_sw_activity;
enum sw_activity {
OFF,
BLINK_ON,
BLINK_OFF,
};
#ifdef CONFIG_ATA_SFF
struct ata_ioports {
@ -701,6 +715,7 @@ struct ata_port {
struct timer_list fastdrain_timer;
unsigned long fastdrain_cnt;
int em_message_type;
void *private_data;
#ifdef CONFIG_ATA_ACPI
@ -792,6 +807,12 @@ struct ata_port_operations {
u8 (*bmdma_status)(struct ata_port *ap);
#endif /* CONFIG_ATA_SFF */
ssize_t (*em_show)(struct ata_port *ap, char *buf);
ssize_t (*em_store)(struct ata_port *ap, const char *message,
size_t size);
ssize_t (*sw_activity_show)(struct ata_device *dev, char *buf);
ssize_t (*sw_activity_store)(struct ata_device *dev,
enum sw_activity val);
/*
* Obsolete
*/