diff options
Diffstat (limited to 'drivers/misc/hpilo.c')
-rw-r--r-- | drivers/misc/hpilo.c | 290 |
1 files changed, 203 insertions, 87 deletions
diff --git a/drivers/misc/hpilo.c b/drivers/misc/hpilo.c index 880ccf39e23..1ad27c6abcc 100644 --- a/drivers/misc/hpilo.c +++ b/drivers/misc/hpilo.c @@ -13,6 +13,7 @@ #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> @@ -21,6 +22,8 @@ #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 +64,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 +75,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 +83,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 +98,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 +163,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); @@ -151,6 +179,7 @@ static inline void doorbell_clr(struct ccb *ccb) { iowrite8(2, ccb->ccb_u5.db_base); } + static inline int ctrl_set(int l2sz, int idxmask, int desclim) { int active = 0, go = 1; @@ -160,6 +189,7 @@ static inline int ctrl_set(int l2sz, int idxmask, int desclim) active << CTRL_BITPOS_A | go << CTRL_BITPOS_G; } + static void ctrl_setup(struct ccb *ccb, int nr_desc, int l2desc_sz) { /* for simplicity, use the same parameters for send and recv ctrls */ @@ -192,13 +222,10 @@ static void fifo_setup(void *base_addr, int nr_entry) static void ilo_ccb_close(struct pci_dev *pdev, struct ccb_data *data) { - struct ccb *driver_ccb; - struct ccb __iomem *device_ccb; + struct ccb *driver_ccb = &data->driver_ccb; + struct ccb __iomem *device_ccb = data->mapped_ccb; int retries; - driver_ccb = &data->driver_ccb; - device_ccb = data->mapped_ccb; - /* complicated dance to tell the hw we are stopping */ doorbell_clr(driver_ccb); iowrite32(ioread32(&device_ccb->send_ctrl) & ~(1 << CTRL_BITPOS_G), @@ -225,26 +252,22 @@ static void ilo_ccb_close(struct pci_dev *pdev, struct ccb_data *data) pci_free_consistent(pdev, data->dma_size, data->dma_va, data->dma_pa); } -static int ilo_ccb_open(struct ilo_hwinfo *hw, struct ccb_data *data, int slot) +static int ilo_ccb_setup(struct ilo_hwinfo *hw, struct ccb_data *data, int slot) { char *dma_va, *dma_pa; - int pkt_id, pkt_sz, i, error; struct ccb *driver_ccb, *ilo_ccb; - struct pci_dev *pdev; driver_ccb = &data->driver_ccb; ilo_ccb = &data->ilo_ccb; - pdev = hw->ilo_dev; data->dma_size = 2 * fifo_sz(NR_QENTRY) + 2 * desc_mem_sz(NR_QENTRY) + ILO_START_ALIGN + ILO_CACHE_SZ; - error = -ENOMEM; - data->dma_va = pci_alloc_consistent(pdev, data->dma_size, + data->dma_va = pci_alloc_consistent(hw->ilo_dev, data->dma_size, &data->dma_pa); if (!data->dma_va) - goto out; + return -ENOMEM; dma_va = (char *)data->dma_va; dma_pa = (char *)data->dma_pa; @@ -290,10 +313,18 @@ static int ilo_ccb_open(struct ilo_hwinfo *hw, struct ccb_data *data, int slot) driver_ccb->ccb_u5.db_base = hw->db_vaddr + (slot << L2_DB_SIZE); ilo_ccb->ccb_u5.db_base = NULL; /* hw ccb's doorbell is not used */ + return 0; +} + +static void ilo_ccb_open(struct ilo_hwinfo *hw, struct ccb_data *data, int slot) +{ + int pkt_id, pkt_sz; + struct ccb *driver_ccb = &data->driver_ccb; + /* copy the ccb with physical addrs to device memory */ data->mapped_ccb = (struct ccb __iomem *) (hw->ram_vaddr + (slot * ILOHW_CCB_SZ)); - memcpy_toio(data->mapped_ccb, ilo_ccb, sizeof(struct ccb)); + memcpy_toio(data->mapped_ccb, &data->ilo_ccb, sizeof(struct ccb)); /* put packets on the send and receive queues */ pkt_sz = 0; @@ -306,7 +337,14 @@ static int ilo_ccb_open(struct ilo_hwinfo *hw, struct ccb_data *data, int slot) for (pkt_id = 0; pkt_id < NR_QENTRY; pkt_id++) ilo_pkt_enqueue(hw, driver_ccb, RECVQ, pkt_id, pkt_sz); + /* the ccb is ready to use */ doorbell_clr(driver_ccb); +} + +static int ilo_ccb_verify(struct ilo_hwinfo *hw, struct ccb_data *data) +{ + int pkt_id, i; + struct ccb *driver_ccb = &data->driver_ccb; /* make sure iLO is really handling requests */ for (i = MAX_WAIT; i > 0; i--) { @@ -315,20 +353,14 @@ static int ilo_ccb_open(struct ilo_hwinfo *hw, struct ccb_data *data, int slot) udelay(WAIT_TIME); } - if (i) { - ilo_pkt_enqueue(hw, driver_ccb, SENDQ, pkt_id, 0); - doorbell_set(driver_ccb); - } else { - dev_err(&pdev->dev, "Open could not dequeue a packet\n"); - error = -EBUSY; - goto free; + if (i == 0) { + dev_err(&hw->ilo_dev->dev, "Open could not dequeue a packet\n"); + return -EBUSY; } + ilo_pkt_enqueue(hw, driver_ccb, SENDQ, pkt_id, 0); + doorbell_set(driver_ccb); return 0; -free: - ilo_ccb_close(pdev, data); -out: - return error; } static inline int is_channel_reset(struct ccb *ccb) @@ -343,19 +375,45 @@ static inline void set_channel_reset(struct ccb *ccb) FIFOBARTOHANDLE(ccb->ccb_u1.send_fifobar)->reset = 1; } +static inline int get_device_outbound(struct ilo_hwinfo *hw) +{ + return ioread32(&hw->mmio_vaddr[DB_OUT]); +} + +static inline int is_db_reset(int db_out) +{ + return db_out & (1 << DB_RESET); +} + static inline int is_device_reset(struct ilo_hwinfo *hw) { /* check for global reset condition */ - return ioread32(&hw->mmio_vaddr[DB_OUT]) & (1 << DB_RESET); + return is_db_reset(get_device_outbound(hw)); +} + +static inline void clear_pending_db(struct ilo_hwinfo *hw, int clr) +{ + iowrite32(clr, &hw->mmio_vaddr[DB_OUT]); } static inline void clear_device(struct ilo_hwinfo *hw) { /* clear the device (reset bits, pending channel entries) */ - iowrite32(-1, &hw->mmio_vaddr[DB_OUT]); + clear_pending_db(hw, -1); +} + +static inline void ilo_enable_interrupts(struct ilo_hwinfo *hw) +{ + iowrite8(ioread8(&hw->mmio_vaddr[DB_IRQ]) | 1, &hw->mmio_vaddr[DB_IRQ]); } -static void ilo_locked_reset(struct ilo_hwinfo *hw) +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; @@ -368,40 +426,22 @@ 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, size_t len, loff_t *off) { int err, found, cnt, pkt_id, pkt_len; - struct ccb_data *data; - struct ccb *driver_ccb; - struct ilo_hwinfo *hw; + struct ccb_data *data = fp->private_data; + struct ccb *driver_ccb = &data->driver_ccb; + struct ilo_hwinfo *hw = data->ilo_hw; void *pkt; - data = fp->private_data; - driver_ccb = &data->driver_ccb; - hw = data->ilo_hw; - - 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; } @@ -442,23 +482,13 @@ static ssize_t ilo_write(struct file *fp, const char __user *buf, size_t len, loff_t *off) { int err, pkt_id, pkt_len; - struct ccb_data *data; - struct ccb *driver_ccb; - struct ilo_hwinfo *hw; + struct ccb_data *data = fp->private_data; + struct ccb *driver_ccb = &data->driver_ccb; + struct ilo_hwinfo *hw = data->ilo_hw; void *pkt; - data = fp->private_data; - driver_ccb = &data->driver_ccb; - hw = data->ilo_hw; - - 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)) @@ -480,32 +510,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; } @@ -515,6 +561,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); @@ -524,22 +571,42 @@ 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) { /* create a channel control block for this minor */ - error = ilo_ccb_open(hw, data, slot); - if (!error) { - hw->ccb_alloc[slot] = data; - hw->ccb_alloc[slot]->ccb_cnt = 1; - hw->ccb_alloc[slot]->ccb_excl = fp->f_flags & O_EXCL; - hw->ccb_alloc[slot]->ilo_hw = hw; - } else + error = ilo_ccb_setup(hw, data, slot); + if (error) { kfree(data); + 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; + } + } else { kfree(data); if (fp->f_flags & O_EXCL || hw->ccb_alloc[slot]->ccb_excl) { @@ -554,7 +621,8 @@ static int ilo_open(struct inode *ip, struct file *fp) error = 0; } } - spin_unlock(&hw->alloc_lock); +out: + spin_unlock(&hw->open_lock); if (!error) fp->private_data = hw->ccb_alloc[slot]; @@ -566,10 +634,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); @@ -623,6 +727,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); @@ -658,6 +764,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) @@ -676,13 +783,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++) { @@ -695,6 +808,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: @@ -759,7 +875,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>"); |