Re: [patch] ata: ahci: Enclosure Management via LED

From: Kristen Carlson Accardi
Date: Thu Nov 29 2007 - 13:48:06 EST


On Thu, 29 Nov 2007 09:48:02 -0800
Kristen Carlson Accardi <kristen.c.accardi@xxxxxxxxx> wrote:

> This patch implements Enclosure Management via the LED protocol. See
> the AHCI 1.1 spec for details.

Whoops, I totally messed up and sent the wrong version of this patch.
I'll send an updated one, ignore this.

Kristen


>
> Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@xxxxxxxxx>
> ---
> Here's a new version of the Enclosure management patch I sent a few
> weeks ago. I tried to incorporate all the feedback, although I'm
> still checking on whether it's ok to use these capability bits on
> a 1.0 ahci device. Please let me know if there are additional
> changes needed.
>
> drivers/ata/ahci.c | 152
> ++++++++++++++++++++++++++++++++++++++++++++-
> drivers/ata/libata-scsi.c | 5 +- include/linux/libata.h | 2 +
> 3 files changed, 155 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c
> index 2c686b4..5f22132 100644
> --- a/drivers/ata/ahci.c
> +++ b/drivers/ata/ahci.c
> @@ -44,6 +44,7 @@
> #include <linux/dmi.h>
> #include <scsi/scsi_host.h>
> #include <scsi/scsi_cmnd.h>
> +#include <scsi/scsi_device.h>
> #include <linux/libata.h>
>
> #define DRV_NAME "ahci"
> @@ -92,6 +93,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 */ @@ -99,6 +102,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 */ @@ -193,6 +197,10 @@ enum {
> ATA_FLAG_ACPI_SATA |
> ATA_FLAG_AN | ATA_FLAG_IPM,
> AHCI_LFLAG_COMMON = ATA_LFLAG_SKIP_D2H_BSY,
> +
> + /* em_ctl bits */
> + EM_CTL_RST = (1 << 9), /* Reset */
> + EM_CTL_TM = (1 << 8), /* Transmit
> Message */ };
>
> struct ahci_cmd_hdr {
> @@ -216,6 +224,7 @@ struct ahci_host_priv {
> 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 {
> @@ -231,6 +240,7 @@ struct ahci_port_priv {
> unsigned int ncq_saw_dmas:1;
> unsigned int ncq_saw_sdb:1;
> u32 intr_mask; /* interrupts
> to enable */
> + u16 led_state; /* saved
> current led state */ };
>
> static int ahci_scr_read(struct ata_port *ap, unsigned int sc_reg,
> u32 *val); @@ -572,6 +582,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)
> {
> @@ -1082,6 +1097,116 @@ static int ahci_reset_controller(struct
> ata_host *host) return 0;
> }
>
> +/****** LED Enclosure Management routines ********/
> +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 int ahci_transmit_led_message(struct ata_port *ap, int
> led_num,
> + int state)
> +{
> + struct ahci_host_priv *hpriv = ap->host->private_data;
> + void __iomem *mmio = ap->host->iomap[AHCI_PCI_BAR];
> + struct ahci_port_priv *pp = ap->private_data;
> + u32 em_ctl;
> + u32 message[] = {0, 0};
> + unsigned int flags;
> +
> + 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);
> +
> + pp->led_state &= ~(9 << (3*led_num));
> +
> + /*
> + * create the actual message
> + * XXX will need Port Multiplier support
> + */
> + message[1] = (ap->port_no | (pp->led_state << 16));
> +
> + /* LED bit locations are determined by the led_num */
> + message[1] |= (state << (16 + (3*led_num)));
> +
> + /* 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 */
> + pp->led_state = ((message[1] >> 16) & 0x00ff);
> +
> + /*
> + * tell hardware to transmit the message
> + */
> + writel(em_ctl | EM_CTL_TM, mmio + HOST_EM_CTL);
> +
> + spin_unlock_irqrestore(ap->lock, flags);
> + return 0;
> +}
> +
> +static ssize_t ahci_led_store(struct device *dev, const char *buf,
> int num) +{
> + 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);
> + int state;
> + int rc;
> +
> + if (!atadev || !ata_dev_enabled(atadev))
> + return -EINVAL;
> +
> + state = simple_strtoul(buf, NULL, 0);
> + if (state != 0 && state != 1)
> + return -EINVAL;
> +
> + rc = ahci_transmit_led_message(ap, num, state);
> + if (!rc)
> + return count;
> + return rc;
> +}
> +
> +static ssize_t ahci_led_locate_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t count)
> +{
> + ahci_led_store(dev, buf, 1);
> +}
> +static DEVICE_ATTR(locate, S_IWUSR | S_IRUGO, NULL,
> ahci_led_locate_store); +
> +static ssize_t ahci_led_fault_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t count)
> +{
> + ahci_led_store(dev, buf, 2);
> +}
> +static DEVICE_ATTR(fault, S_IWUGO, NULL, ahci_led_fault_store);
> +
> +static struct device_attribute *ahci_em_led_attrs[] = {
> + &dev_attr_locate,
> + &dev_attr_fault,
> + NULL
> +};
> +
> static void ahci_port_init(struct pci_dev *pdev, struct ata_port *ap,
> int port_no, void __iomem *mmio,
> void __iomem *port_mmio)
> @@ -2178,7 +2303,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 " : "",
> @@ -2195,7 +2321,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 ": ""
> );
> }
>
> @@ -2331,6 +2458,27 @@ static int ahci_init_one(struct pci_dev *pdev,
> const struct pci_device_id *ent) ahci_init_controller(host);
> ahci_print_info(host);
>
> + if (ahci_em_messages && (hpriv->cap & HOST_CAP_EMS)) {
> + u8 messages;
> + void __iomem *mmio = host->iomap[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);
> +
> + /* reset the LEDs */
> + ahci_reset_em(host);
> +
> + /* modify sht to add led sysfs files */
> + ahci_sht.sdev_attrs = ahci_em_led_attrs;
> + }
> + }
> +
> pci_set_master(pdev);
> return ata_host_activate(host, pdev->irq, ahci_interrupt,
> IRQF_SHARED, &ahci_sht);
> diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
> index fad236d..a49dc19 100644
> --- a/drivers/ata/libata-scsi.c
> +++ b/drivers/ata/libata-scsi.c
> @@ -55,7 +55,7 @@ typedef unsigned int (*ata_xlat_func_t)(struct
> ata_queued_cmd *qc);
> 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, +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);
> @@ -2593,7 +2593,7 @@ static int ata_scsi_dev_enabled(struct
> ata_device *dev)
> * RETURNS:
> * Associated ATA device, or %NULL if not found.
> */
> -static struct ata_device *
> +struct ata_device *
> ata_scsi_find_dev(struct ata_port *ap, const struct scsi_device
> *scsidev) {
> struct ata_device *dev = __ata_scsi_find_dev(ap, scsidev);
> @@ -2603,6 +2603,7 @@ ata_scsi_find_dev(struct ata_port *ap, const
> struct scsi_device *scsidev)
> return dev;
> }
> +EXPORT_SYMBOL_GPL(ata_scsi_find_dev);
>
> /*
> * ata_scsi_map_proto - Map pass-thru protocol value to
> taskfile value. diff --git a/include/linux/libata.h
> b/include/linux/libata.h index ef52a07..1ccbd83 100644
> --- a/include/linux/libata.h
> +++ b/include/linux/libata.h
> @@ -900,6 +900,8 @@ extern int ata_scsi_slave_config(struct
> scsi_device *sdev); extern void ata_scsi_slave_destroy(struct
> scsi_device *sdev); extern int ata_scsi_change_queue_depth(struct
> scsi_device *sdev, int queue_depth);
> +struct ata_device *ata_scsi_find_dev(struct ata_port *ap,
> + const struct scsi_device
> *scsidev); extern struct ata_device *ata_dev_pair(struct ata_device
> *adev); extern int ata_do_set_mode(struct ata_link *link, struct
> ata_device **r_failed_dev); extern u8 ata_irq_on(struct ata_port *ap);
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/