be2net: implement EEH pci error recovery handlers
[safe/jmp/linux-2.6] / drivers / misc / hpilo.c
index 35ed123..a92a3a7 100644 (file)
 #include <linux/module.h>
 #include <linux/fs.h>
 #include <linux/pci.h>
+#include <linux/interrupt.h>
 #include <linux/ioport.h>
 #include <linux/device.h>
 #include <linux/file.h>
 #include <linux/cdev.h>
+#include <linux/sched.h>
 #include <linux/spinlock.h>
 #include <linux/delay.h>
 #include <linux/uaccess.h>
 #include <linux/io.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
 #include "hpilo.h"
 
 static struct class *ilo_class;
@@ -61,9 +65,10 @@ static inline int desc_mem_sz(int nr_entry)
 static int fifo_enqueue(struct ilo_hwinfo *hw, char *fifobar, int entry)
 {
        struct fifo *fifo_q = FIFOBARTOHANDLE(fifobar);
+       unsigned long flags;
        int ret = 0;
 
-       spin_lock(&hw->fifo_lock);
+       spin_lock_irqsave(&hw->fifo_lock, flags);
        if (!(fifo_q->fifobar[(fifo_q->tail + 1) & fifo_q->imask]
              & ENTRY_MASK_O)) {
                fifo_q->fifobar[fifo_q->tail & fifo_q->imask] |=
@@ -71,7 +76,7 @@ static int fifo_enqueue(struct ilo_hwinfo *hw, char *fifobar, int entry)
                fifo_q->tail += 1;
                ret = 1;
        }
-       spin_unlock(&hw->fifo_lock);
+       spin_unlock_irqrestore(&hw->fifo_lock, flags);
 
        return ret;
 }
@@ -79,10 +84,11 @@ static int fifo_enqueue(struct ilo_hwinfo *hw, char *fifobar, int entry)
 static int fifo_dequeue(struct ilo_hwinfo *hw, char *fifobar, int *entry)
 {
        struct fifo *fifo_q = FIFOBARTOHANDLE(fifobar);
+       unsigned long flags;
        int ret = 0;
        u64 c;
 
-       spin_lock(&hw->fifo_lock);
+       spin_lock_irqsave(&hw->fifo_lock, flags);
        c = fifo_q->fifobar[fifo_q->head & fifo_q->imask];
        if (c & ENTRY_MASK_C) {
                if (entry)
@@ -93,7 +99,23 @@ static int fifo_dequeue(struct ilo_hwinfo *hw, char *fifobar, int *entry)
                fifo_q->head += 1;
                ret = 1;
        }
-       spin_unlock(&hw->fifo_lock);
+       spin_unlock_irqrestore(&hw->fifo_lock, flags);
+
+       return ret;
+}
+
+static int fifo_check_recv(struct ilo_hwinfo *hw, char *fifobar)
+{
+       struct fifo *fifo_q = FIFOBARTOHANDLE(fifobar);
+       unsigned long flags;
+       int ret = 0;
+       u64 c;
+
+       spin_lock_irqsave(&hw->fifo_lock, flags);
+       c = fifo_q->fifobar[fifo_q->head & fifo_q->imask];
+       if (c & ENTRY_MASK_C)
+               ret = 1;
+       spin_unlock_irqrestore(&hw->fifo_lock, flags);
 
        return ret;
 }
@@ -142,6 +164,13 @@ static int ilo_pkt_dequeue(struct ilo_hwinfo *hw, struct ccb *ccb,
        return ret;
 }
 
+static int ilo_pkt_recv(struct ilo_hwinfo *hw, struct ccb *ccb)
+{
+       char *fifobar = ccb->ccb_u3.recv_fifobar;
+
+       return fifo_check_recv(hw, fifobar);
+}
+
 static inline void doorbell_set(struct ccb *ccb)
 {
        iowrite8(1, ccb->ccb_u5.db_base);
@@ -374,7 +403,18 @@ static inline void clear_device(struct ilo_hwinfo *hw)
        clear_pending_db(hw, -1);
 }
 
-static void ilo_locked_reset(struct ilo_hwinfo *hw)
+static inline void ilo_enable_interrupts(struct ilo_hwinfo *hw)
+{
+       iowrite8(ioread8(&hw->mmio_vaddr[DB_IRQ]) | 1, &hw->mmio_vaddr[DB_IRQ]);
+}
+
+static inline void ilo_disable_interrupts(struct ilo_hwinfo *hw)
+{
+       iowrite8(ioread8(&hw->mmio_vaddr[DB_IRQ]) & ~1,
+                &hw->mmio_vaddr[DB_IRQ]);
+}
+
+static void ilo_set_reset(struct ilo_hwinfo *hw)
 {
        int slot;
 
@@ -387,19 +427,6 @@ static void ilo_locked_reset(struct ilo_hwinfo *hw)
                        continue;
                set_channel_reset(&hw->ccb_alloc[slot]->driver_ccb);
        }
-
-       clear_device(hw);
-}
-
-static void ilo_reset(struct ilo_hwinfo *hw)
-{
-       spin_lock(&hw->alloc_lock);
-
-       /* reset might have been handled after lock was taken */
-       if (is_device_reset(hw))
-               ilo_locked_reset(hw);
-
-       spin_unlock(&hw->alloc_lock);
 }
 
 static ssize_t ilo_read(struct file *fp, char __user *buf,
@@ -411,12 +438,11 @@ static ssize_t ilo_read(struct file *fp, char __user *buf,
        struct ilo_hwinfo *hw = data->ilo_hw;
        void *pkt;
 
-       if (is_device_reset(hw) || is_channel_reset(driver_ccb)) {
+       if (is_channel_reset(driver_ccb)) {
                /*
                 * If the device has been reset, applications
                 * need to close and reopen all ccbs.
                 */
-               ilo_reset(hw);
                return -ENODEV;
        }
 
@@ -462,14 +488,8 @@ static ssize_t ilo_write(struct file *fp, const char __user *buf,
        struct ilo_hwinfo *hw = data->ilo_hw;
        void *pkt;
 
-       if (is_device_reset(hw) || is_channel_reset(driver_ccb)) {
-               /*
-                * If the device has been reset, applications
-                * need to close and reopen all ccbs.
-                */
-               ilo_reset(hw);
+       if (is_channel_reset(driver_ccb))
                return -ENODEV;
-       }
 
        /* get a packet to send the user command */
        if (!ilo_pkt_dequeue(hw, driver_ccb, SENDQ, &pkt_id, &pkt_len, &pkt))
@@ -491,32 +511,48 @@ static ssize_t ilo_write(struct file *fp, const char __user *buf,
        return err ? -EFAULT : len;
 }
 
+static unsigned int ilo_poll(struct file *fp, poll_table *wait)
+{
+       struct ccb_data *data = fp->private_data;
+       struct ccb *driver_ccb = &data->driver_ccb;
+
+       poll_wait(fp, &data->ccb_waitq, wait);
+
+       if (is_channel_reset(driver_ccb))
+               return POLLERR;
+       else if (ilo_pkt_recv(data->ilo_hw, driver_ccb))
+               return POLLIN | POLLRDNORM;
+
+       return 0;
+}
+
 static int ilo_close(struct inode *ip, struct file *fp)
 {
        int slot;
        struct ccb_data *data;
        struct ilo_hwinfo *hw;
+       unsigned long flags;
 
        slot = iminor(ip) % MAX_CCB;
        hw = container_of(ip->i_cdev, struct ilo_hwinfo, cdev);
 
-       spin_lock(&hw->alloc_lock);
-
-       if (is_device_reset(hw))
-               ilo_locked_reset(hw);
+       spin_lock(&hw->open_lock);
 
        if (hw->ccb_alloc[slot]->ccb_cnt == 1) {
 
                data = fp->private_data;
 
+               spin_lock_irqsave(&hw->alloc_lock, flags);
+               hw->ccb_alloc[slot] = NULL;
+               spin_unlock_irqrestore(&hw->alloc_lock, flags);
+
                ilo_ccb_close(hw->ilo_dev, data);
 
                kfree(data);
-               hw->ccb_alloc[slot] = NULL;
        } else
                hw->ccb_alloc[slot]->ccb_cnt--;
 
-       spin_unlock(&hw->alloc_lock);
+       spin_unlock(&hw->open_lock);
 
        return 0;
 }
@@ -526,6 +562,7 @@ static int ilo_open(struct inode *ip, struct file *fp)
        int slot, error;
        struct ccb_data *data;
        struct ilo_hwinfo *hw;
+       unsigned long flags;
 
        slot = iminor(ip) % MAX_CCB;
        hw = container_of(ip->i_cdev, struct ilo_hwinfo, cdev);
@@ -535,10 +572,7 @@ static int ilo_open(struct inode *ip, struct file *fp)
        if (!data)
                return -ENOMEM;
 
-       spin_lock(&hw->alloc_lock);
-
-       if (is_device_reset(hw))
-               ilo_locked_reset(hw);
+       spin_lock(&hw->open_lock);
 
        /* each fd private_data holds sw/hw view of ccb */
        if (hw->ccb_alloc[slot] == NULL) {
@@ -549,22 +583,31 @@ static int ilo_open(struct inode *ip, struct file *fp)
                        goto out;
                }
 
+               data->ccb_cnt = 1;
+               data->ccb_excl = fp->f_flags & O_EXCL;
+               data->ilo_hw = hw;
+               init_waitqueue_head(&data->ccb_waitq);
+
                /* write the ccb to hw */
+               spin_lock_irqsave(&hw->alloc_lock, flags);
                ilo_ccb_open(hw, data, slot);
+               hw->ccb_alloc[slot] = data;
+               spin_unlock_irqrestore(&hw->alloc_lock, flags);
 
                /* make sure the channel is functional */
                error = ilo_ccb_verify(hw, data);
                if (error) {
+
+                       spin_lock_irqsave(&hw->alloc_lock, flags);
+                       hw->ccb_alloc[slot] = NULL;
+                       spin_unlock_irqrestore(&hw->alloc_lock, flags);
+
                        ilo_ccb_close(hw->ilo_dev, data);
+
                        kfree(data);
                        goto out;
                }
 
-               data->ccb_cnt = 1;
-               data->ccb_excl = fp->f_flags & O_EXCL;
-               data->ilo_hw = hw;
-               hw->ccb_alloc[slot] = data;
-
        } else {
                kfree(data);
                if (fp->f_flags & O_EXCL || hw->ccb_alloc[slot]->ccb_excl) {
@@ -580,7 +623,7 @@ static int ilo_open(struct inode *ip, struct file *fp)
                }
        }
 out:
-       spin_unlock(&hw->alloc_lock);
+       spin_unlock(&hw->open_lock);
 
        if (!error)
                fp->private_data = hw->ccb_alloc[slot];
@@ -592,10 +635,46 @@ static const struct file_operations ilo_fops = {
        .owner          = THIS_MODULE,
        .read           = ilo_read,
        .write          = ilo_write,
+       .poll           = ilo_poll,
        .open           = ilo_open,
        .release        = ilo_close,
 };
 
+static irqreturn_t ilo_isr(int irq, void *data)
+{
+       struct ilo_hwinfo *hw = data;
+       int pending, i;
+
+       spin_lock(&hw->alloc_lock);
+
+       /* check for ccbs which have data */
+       pending = get_device_outbound(hw);
+       if (!pending) {
+               spin_unlock(&hw->alloc_lock);
+               return IRQ_NONE;
+       }
+
+       if (is_db_reset(pending)) {
+               /* wake up all ccbs if the device was reset */
+               pending = -1;
+               ilo_set_reset(hw);
+       }
+
+       for (i = 0; i < MAX_CCB; i++) {
+               if (!hw->ccb_alloc[i])
+                       continue;
+               if (pending & (1 << i))
+                       wake_up_interruptible(&hw->ccb_alloc[i]->ccb_waitq);
+       }
+
+       /* clear the device of the channels that have been handled */
+       clear_pending_db(hw, pending);
+
+       spin_unlock(&hw->alloc_lock);
+
+       return IRQ_HANDLED;
+}
+
 static void ilo_unmap_device(struct pci_dev *pdev, struct ilo_hwinfo *hw)
 {
        pci_iounmap(pdev, hw->db_vaddr);
@@ -649,6 +728,8 @@ static void ilo_remove(struct pci_dev *pdev)
                device_destroy(ilo_class, MKDEV(ilo_major, i));
 
        cdev_del(&ilo_hw->cdev);
+       ilo_disable_interrupts(ilo_hw);
+       free_irq(pdev->irq, ilo_hw);
        ilo_unmap_device(pdev, ilo_hw);
        pci_release_regions(pdev);
        pci_disable_device(pdev);
@@ -684,6 +765,7 @@ static int __devinit ilo_probe(struct pci_dev *pdev,
        ilo_hw->ilo_dev = pdev;
        spin_lock_init(&ilo_hw->alloc_lock);
        spin_lock_init(&ilo_hw->fifo_lock);
+       spin_lock_init(&ilo_hw->open_lock);
 
        error = pci_enable_device(pdev);
        if (error)
@@ -702,13 +784,19 @@ static int __devinit ilo_probe(struct pci_dev *pdev,
        pci_set_drvdata(pdev, ilo_hw);
        clear_device(ilo_hw);
 
+       error = request_irq(pdev->irq, ilo_isr, IRQF_SHARED, "hpilo", ilo_hw);
+       if (error)
+               goto unmap;
+
+       ilo_enable_interrupts(ilo_hw);
+
        cdev_init(&ilo_hw->cdev, &ilo_fops);
        ilo_hw->cdev.owner = THIS_MODULE;
        start = devnum * MAX_CCB;
        error = cdev_add(&ilo_hw->cdev, MKDEV(ilo_major, start), MAX_CCB);
        if (error) {
                dev_err(&pdev->dev, "Could not add cdev\n");
-               goto unmap;
+               goto remove_isr;
        }
 
        for (minor = 0 ; minor < MAX_CCB; minor++) {
@@ -721,6 +809,9 @@ static int __devinit ilo_probe(struct pci_dev *pdev,
        }
 
        return 0;
+remove_isr:
+       ilo_disable_interrupts(ilo_hw);
+       free_irq(pdev->irq, ilo_hw);
 unmap:
        ilo_unmap_device(pdev, ilo_hw);
 free_regions:
@@ -785,7 +876,7 @@ static void __exit ilo_exit(void)
        class_destroy(ilo_class);
 }
 
-MODULE_VERSION("1.1");
+MODULE_VERSION("1.2");
 MODULE_ALIAS(ILO_NAME);
 MODULE_DESCRIPTION(ILO_NAME);
 MODULE_AUTHOR("David Altobelli <david.altobelli@hp.com>");