[PATCH 2/11] drivers/watchdog: Eliminate a NULL pointer dereference
[safe/jmp/linux-2.6] / drivers / ata / libahci.c
index 38e1b4e..1984a6e 100644 (file)
@@ -33,6 +33,7 @@
  */
 
 #include <linux/kernel.h>
+#include <linux/gfp.h>
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/blkdev.h>
@@ -107,11 +108,18 @@ static ssize_t ahci_show_host_version(struct device *dev,
                                      struct device_attribute *attr, char *buf);
 static ssize_t ahci_show_port_cmd(struct device *dev,
                                  struct device_attribute *attr, char *buf);
+static ssize_t ahci_read_em_buffer(struct device *dev,
+                                  struct device_attribute *attr, char *buf);
+static ssize_t ahci_store_em_buffer(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t size);
 
 static DEVICE_ATTR(ahci_host_caps, S_IRUGO, ahci_show_host_caps, NULL);
 static DEVICE_ATTR(ahci_host_cap2, S_IRUGO, ahci_show_host_cap2, NULL);
 static DEVICE_ATTR(ahci_host_version, S_IRUGO, ahci_show_host_version, NULL);
 static DEVICE_ATTR(ahci_port_cmd, S_IRUGO, ahci_show_port_cmd, NULL);
+static DEVICE_ATTR(em_buffer, S_IWUSR | S_IRUGO,
+                  ahci_read_em_buffer, ahci_store_em_buffer);
 
 static struct device_attribute *ahci_shost_attrs[] = {
        &dev_attr_link_power_management_policy,
@@ -121,6 +129,7 @@ static struct device_attribute *ahci_shost_attrs[] = {
        &dev_attr_ahci_host_cap2,
        &dev_attr_ahci_host_version,
        &dev_attr_ahci_port_cmd,
+       &dev_attr_em_buffer,
        NULL
 };
 
@@ -183,7 +192,7 @@ EXPORT_SYMBOL_GPL(ahci_em_messages);
 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");
+       "AHCI Enclosure Management Message control (0 = off, 1 = on)");
 
 static void ahci_enable_ahci(void __iomem *mmio)
 {
@@ -251,6 +260,101 @@ static ssize_t ahci_show_port_cmd(struct device *dev,
        return sprintf(buf, "%x\n", readl(port_mmio + PORT_CMD));
 }
 
+static ssize_t ahci_read_em_buffer(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);
+       struct ahci_host_priv *hpriv = ap->host->private_data;
+       void __iomem *mmio = hpriv->mmio;
+       void __iomem *em_mmio = mmio + hpriv->em_loc;
+       u32 em_ctl, msg;
+       unsigned long flags;
+       size_t count;
+       int i;
+
+       spin_lock_irqsave(ap->lock, flags);
+
+       em_ctl = readl(mmio + HOST_EM_CTL);
+       if (!(ap->flags & ATA_FLAG_EM) || em_ctl & EM_CTL_XMT ||
+           !(hpriv->em_msg_type & EM_MSG_TYPE_SGPIO)) {
+               spin_unlock_irqrestore(ap->lock, flags);
+               return -EINVAL;
+       }
+
+       if (!(em_ctl & EM_CTL_MR)) {
+               spin_unlock_irqrestore(ap->lock, flags);
+               return -EAGAIN;
+       }
+
+       if (!(em_ctl & EM_CTL_SMB))
+               em_mmio += hpriv->em_buf_sz;
+
+       count = hpriv->em_buf_sz;
+
+       /* the count should not be larger than PAGE_SIZE */
+       if (count > PAGE_SIZE) {
+               if (printk_ratelimit())
+                       ata_port_printk(ap, KERN_WARNING,
+                                       "EM read buffer size too large: "
+                                       "buffer size %u, page size %lu\n",
+                                       hpriv->em_buf_sz, PAGE_SIZE);
+               count = PAGE_SIZE;
+       }
+
+       for (i = 0; i < count; i += 4) {
+               msg = readl(em_mmio + i);
+               buf[i] = msg & 0xff;
+               buf[i + 1] = (msg >> 8) & 0xff;
+               buf[i + 2] = (msg >> 16) & 0xff;
+               buf[i + 3] = (msg >> 24) & 0xff;
+       }
+
+       spin_unlock_irqrestore(ap->lock, flags);
+
+       return i;
+}
+
+static ssize_t ahci_store_em_buffer(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t size)
+{
+       struct Scsi_Host *shost = class_to_shost(dev);
+       struct ata_port *ap = ata_shost_to_port(shost);
+       struct ahci_host_priv *hpriv = ap->host->private_data;
+       void __iomem *mmio = hpriv->mmio;
+       void __iomem *em_mmio = mmio + hpriv->em_loc;
+       u32 em_ctl, msg;
+       unsigned long flags;
+       int i;
+
+       /* check size validity */
+       if (!(ap->flags & ATA_FLAG_EM) ||
+           !(hpriv->em_msg_type & EM_MSG_TYPE_SGPIO) ||
+           size % 4 || size > hpriv->em_buf_sz)
+               return -EINVAL;
+
+       spin_lock_irqsave(ap->lock, flags);
+
+       em_ctl = readl(mmio + HOST_EM_CTL);
+       if (em_ctl & EM_CTL_TM) {
+               spin_unlock_irqrestore(ap->lock, flags);
+               return -EBUSY;
+       }
+
+       for (i = 0; i < size; i += 4) {
+               msg = buf[i] | buf[i + 1] << 8 |
+                     buf[i + 2] << 16 | buf[i + 3] << 24;
+               writel(msg, em_mmio + i);
+       }
+
+       writel(em_ctl | EM_CTL_TM, mmio + HOST_EM_CTL);
+
+       spin_unlock_irqrestore(ap->lock, flags);
+
+       return size;
+}
+
 /**
  *     ahci_save_initial_config - Save and fixup initial config values
  *     @dev: target AHCI device
@@ -437,11 +541,29 @@ static int ahci_scr_write(struct ata_link *link, unsigned int sc_reg, u32 val)
        return -EINVAL;
 }
 
+static int ahci_is_device_present(void __iomem *port_mmio)
+{
+       u8 status = readl(port_mmio + PORT_TFDATA) & 0xff;
+
+       /* Make sure PxTFD.STS.BSY and PxTFD.STS.DRQ are 0 */
+       if (status & (ATA_BUSY | ATA_DRQ))
+               return 0;
+
+       /* Make sure PxSSTS.DET is 3h */
+       status = readl(port_mmio + PORT_SCR_STAT) & 0xf;
+       if (status != 3)
+               return 0;
+       return 1;
+}
+
 void ahci_start_engine(struct ata_port *ap)
 {
        void __iomem *port_mmio = ahci_port_base(ap);
        u32 tmp;
 
+       if (!ahci_is_device_present(port_mmio))
+               return;
+
        /* start DMA */
        tmp = readl(port_mmio + PORT_CMD);
        tmp |= PORT_CMD_START;
@@ -912,27 +1034,29 @@ static ssize_t ahci_transmit_led_message(struct ata_port *ap, u32 state,
                return -EBUSY;
        }
 
-       /*
-        * create message header - this is all zero except for
-        * the message size, which is 4 bytes.
-        */
-       message[0] |= (4 << 8);
+       if (hpriv->em_msg_type & EM_MSG_TYPE_LED) {
+               /*
+                * 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 & ~EM_MSG_LED_HBA_PORT) | ap->port_no);
 
-       /* ignore 0:4 of byte zero, fill in port info yourself */
-       message[1] = ((state & ~EM_MSG_LED_HBA_PORT) | ap->port_no);
+               /* write message to EM_LOC */
+               writel(message[0], mmio + hpriv->em_loc);
+               writel(message[1], mmio + hpriv->em_loc+4);
 
-       /* write message to EM_LOC */
-       writel(message[0], mmio + hpriv->em_loc);
-       writel(message[1], mmio + hpriv->em_loc+4);
+               /*
+                * tell hardware to transmit the message
+                */
+               writel(em_ctl | EM_CTL_TM, mmio + HOST_EM_CTL);
+       }
 
        /* save off new led state for port/slot */
        emp->led_state = state;
 
-       /*
-        * tell hardware to transmit the message
-        */
-       writel(em_ctl | EM_CTL_TM, mmio + HOST_EM_CTL);
-
        spin_unlock_irqrestore(ap->lock, flags);
        return size;
 }
@@ -2075,10 +2199,11 @@ void ahci_set_em_messages(struct ahci_host_priv *hpriv,
 
        messages = (em_ctl & EM_CTRL_MSG_TYPE) >> 16;
 
-       /* we only support LED message type right now */
-       if ((messages & 0x01) && (ahci_em_messages == 1)) {
+       if (messages) {
                /* store em_loc */
                hpriv->em_loc = ((em_loc >> 16) * 4);
+               hpriv->em_buf_sz = ((em_loc & 0xff) * 4);
+               hpriv->em_msg_type = messages;
                pi->flags |= ATA_FLAG_EM;
                if (!(em_ctl & EM_CTL_ALHD))
                        pi->flags |= ATA_FLAG_SW_ACTIVITY;