PCI: handle long delays in VPD access
[firefly-linux-kernel-4.4.55.git] / drivers / pci / access.c
index 39bb96b413ef1b696eae53d6239c2d3a29bbd20e..98ddba94b5b930a6a19d36061190b74f7caadc6c 100644 (file)
@@ -133,39 +133,46 @@ PCI_USER_WRITE_CONFIG(dword, u32)
 
 struct pci_vpd_pci22 {
        struct pci_vpd base;
-       spinlock_t lock; /* controls access to hardware and the flags */
-       u8      cap;
+       struct mutex lock;
+       u16     flag;
        bool    busy;
-       bool    flag; /* value of F bit to wait for */
+       u8      cap;
 };
 
-/* Wait for last operation to complete */
+/*
+ * Wait for last operation to complete.
+ * This code has to spin since there is no other notification from the PCI
+ * hardware. Since the VPD is often implemented by serial attachment to an
+ * EEPROM, it may take many milliseconds to complete.
+ */
 static int pci_vpd_pci22_wait(struct pci_dev *dev)
 {
        struct pci_vpd_pci22 *vpd =
                container_of(dev->vpd, struct pci_vpd_pci22, base);
-       u16 flag, status;
-       int wait;
+       unsigned long timeout = jiffies + HZ/20 + 2;
+       u16 status;
        int ret;
 
        if (!vpd->busy)
                return 0;
 
-       flag = vpd->flag ? PCI_VPD_ADDR_F : 0;
-       wait = vpd->flag ? 10 : 1000; /* read: 100 us; write: 10 ms */
        for (;;) {
-               ret = pci_user_read_config_word(dev,
-                                               vpd->cap + PCI_VPD_ADDR,
+               ret = pci_user_read_config_word(dev, vpd->cap + PCI_VPD_ADDR,
                                                &status);
-               if (ret < 0)
+               if (ret)
                        return ret;
-               if ((status & PCI_VPD_ADDR_F) == flag) {
+
+               if ((status & PCI_VPD_ADDR_F) == vpd->flag) {
                        vpd->busy = false;
                        return 0;
                }
-               if (wait-- == 0)
+
+               if (time_after(jiffies, timeout))
                        return -ETIMEDOUT;
-               udelay(10);
+               if (fatal_signal_pending(current))
+                       return -EINTR;
+               if (!cond_resched())
+                       udelay(10);
        }
 }
 
@@ -175,7 +182,7 @@ static int pci_vpd_pci22_read(struct pci_dev *dev, int pos, int size,
        struct pci_vpd_pci22 *vpd =
                container_of(dev->vpd, struct pci_vpd_pci22, base);
        u32 val;
-       int ret;
+       int ret = 0;
        int begin, end, i;
 
        if (pos < 0 || pos > vpd->base.len || size > vpd->base.len  - pos)
@@ -183,7 +190,9 @@ static int pci_vpd_pci22_read(struct pci_dev *dev, int pos, int size,
        if (size == 0)
                return 0;
 
-       spin_lock_irq(&vpd->lock);
+       if (mutex_lock_killable(&vpd->lock))
+               return -EINTR;
+
        ret = pci_vpd_pci22_wait(dev);
        if (ret < 0)
                goto out;
@@ -191,15 +200,16 @@ static int pci_vpd_pci22_read(struct pci_dev *dev, int pos, int size,
                                         pos & ~3);
        if (ret < 0)
                goto out;
+
        vpd->busy = true;
-       vpd->flag = 1;
+       vpd->flag = PCI_VPD_ADDR_F;
        ret = pci_vpd_pci22_wait(dev);
        if (ret < 0)
                goto out;
        ret = pci_user_read_config_dword(dev, vpd->cap + PCI_VPD_DATA,
                                         &val);
 out:
-       spin_unlock_irq(&vpd->lock);
+       mutex_unlock(&vpd->lock);
        if (ret < 0)
                return ret;
 
@@ -220,7 +230,7 @@ static int pci_vpd_pci22_write(struct pci_dev *dev, int pos, int size,
        struct pci_vpd_pci22 *vpd =
                container_of(dev->vpd, struct pci_vpd_pci22, base);
        u32 val;
-       int ret;
+       int ret = 0;
 
        if (pos < 0 || pos > vpd->base.len || pos & 3 ||
            size > vpd->base.len - pos || size < 4)
@@ -231,7 +241,8 @@ static int pci_vpd_pci22_write(struct pci_dev *dev, int pos, int size,
        val |= ((u8) *buf++) << 16;
        val |= ((u32)(u8) *buf++) << 24;
 
-       spin_lock_irq(&vpd->lock);
+       if (mutex_lock_killable(&vpd->lock))
+               return -EINTR;
        ret = pci_vpd_pci22_wait(dev);
        if (ret < 0)
                goto out;
@@ -247,7 +258,7 @@ static int pci_vpd_pci22_write(struct pci_dev *dev, int pos, int size,
        vpd->flag = 0;
        ret = pci_vpd_pci22_wait(dev);
 out:
-       spin_unlock_irq(&vpd->lock);
+       mutex_unlock(&vpd->lock);
        if (ret < 0)
                return ret;
 
@@ -279,7 +290,7 @@ int pci_vpd_pci22_init(struct pci_dev *dev)
 
        vpd->base.len = PCI_VPD_PCI22_SIZE;
        vpd->base.ops = &pci_vpd_pci22_ops;
-       spin_lock_init(&vpd->lock);
+       mutex_init(&vpd->lock);
        vpd->cap = cap;
        vpd->busy = false;
        dev->vpd = &vpd->base;