kernel-ark/drivers/misc/hpilo.c
David Altobelli 891f7d73ea hpilo: reduce frequency of IO operations
Change hpilo open and close logic to spin for 10usec between checking device,
rather than every usec.

Because the loop is coded to take up to 10ms, it seemed prudent to
increase the interval between polling the device, to reduce the load on
the system and allow more other work to happen.

Signed-off-by: David Altobelli <david.altobelli@hp.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2009-04-01 08:59:18 -07:00

770 lines
18 KiB
C

/*
* Driver for HP iLO/iLO2 management processor.
*
* Copyright (C) 2008 Hewlett-Packard Development Company, L.P.
* David Altobelli <david.altobelli@hp.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/pci.h>
#include <linux/ioport.h>
#include <linux/device.h>
#include <linux/file.h>
#include <linux/cdev.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include "hpilo.h"
static struct class *ilo_class;
static unsigned int ilo_major;
static char ilo_hwdev[MAX_ILO_DEV];
static inline int get_entry_id(int entry)
{
return (entry & ENTRY_MASK_DESCRIPTOR) >> ENTRY_BITPOS_DESCRIPTOR;
}
static inline int get_entry_len(int entry)
{
return ((entry & ENTRY_MASK_QWORDS) >> ENTRY_BITPOS_QWORDS) << 3;
}
static inline int mk_entry(int id, int len)
{
int qlen = len & 7 ? (len >> 3) + 1 : len >> 3;
return id << ENTRY_BITPOS_DESCRIPTOR | qlen << ENTRY_BITPOS_QWORDS;
}
static inline int desc_mem_sz(int nr_entry)
{
return nr_entry << L2_QENTRY_SZ;
}
/*
* FIFO queues, shared with hardware.
*
* If a queue has empty slots, an entry is added to the queue tail,
* and that entry is marked as occupied.
* Entries can be dequeued from the head of the list, when the device
* has marked the entry as consumed.
*
* Returns true on successful queue/dequeue, false on failure.
*/
static int fifo_enqueue(struct ilo_hwinfo *hw, char *fifobar, int entry)
{
struct fifo *fifo_q = FIFOBARTOHANDLE(fifobar);
int ret = 0;
spin_lock(&hw->fifo_lock);
if (!(fifo_q->fifobar[(fifo_q->tail + 1) & fifo_q->imask]
& ENTRY_MASK_O)) {
fifo_q->fifobar[fifo_q->tail & fifo_q->imask] |=
(entry & ENTRY_MASK_NOSTATE) | fifo_q->merge;
fifo_q->tail += 1;
ret = 1;
}
spin_unlock(&hw->fifo_lock);
return ret;
}
static int fifo_dequeue(struct ilo_hwinfo *hw, char *fifobar, int *entry)
{
struct fifo *fifo_q = FIFOBARTOHANDLE(fifobar);
int ret = 0;
u64 c;
spin_lock(&hw->fifo_lock);
c = fifo_q->fifobar[fifo_q->head & fifo_q->imask];
if (c & ENTRY_MASK_C) {
if (entry)
*entry = c & ENTRY_MASK_NOSTATE;
fifo_q->fifobar[fifo_q->head & fifo_q->imask] =
(c | ENTRY_MASK) + 1;
fifo_q->head += 1;
ret = 1;
}
spin_unlock(&hw->fifo_lock);
return ret;
}
static int ilo_pkt_enqueue(struct ilo_hwinfo *hw, struct ccb *ccb,
int dir, int id, int len)
{
char *fifobar;
int entry;
if (dir == SENDQ)
fifobar = ccb->ccb_u1.send_fifobar;
else
fifobar = ccb->ccb_u3.recv_fifobar;
entry = mk_entry(id, len);
return fifo_enqueue(hw, fifobar, entry);
}
static int ilo_pkt_dequeue(struct ilo_hwinfo *hw, struct ccb *ccb,
int dir, int *id, int *len, void **pkt)
{
char *fifobar, *desc;
int entry = 0, pkt_id = 0;
int ret;
if (dir == SENDQ) {
fifobar = ccb->ccb_u1.send_fifobar;
desc = ccb->ccb_u2.send_desc;
} else {
fifobar = ccb->ccb_u3.recv_fifobar;
desc = ccb->ccb_u4.recv_desc;
}
ret = fifo_dequeue(hw, fifobar, &entry);
if (ret) {
pkt_id = get_entry_id(entry);
if (id)
*id = pkt_id;
if (len)
*len = get_entry_len(entry);
if (pkt)
*pkt = (void *)(desc + desc_mem_sz(pkt_id));
}
return ret;
}
static inline void doorbell_set(struct ccb *ccb)
{
iowrite8(1, ccb->ccb_u5.db_base);
}
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;
return l2sz << CTRL_BITPOS_L2SZ |
idxmask << CTRL_BITPOS_FIFOINDEXMASK |
desclim << CTRL_BITPOS_DESCLIMIT |
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 */
ccb->send_ctrl = ctrl_set(l2desc_sz, nr_desc-1, nr_desc-1);
ccb->recv_ctrl = ctrl_set(l2desc_sz, nr_desc-1, nr_desc-1);
}
static inline int fifo_sz(int nr_entry)
{
/* size of a fifo is determined by the number of entries it contains */
return (nr_entry * sizeof(u64)) + FIFOHANDLESIZE;
}
static void fifo_setup(void *base_addr, int nr_entry)
{
struct fifo *fifo_q = base_addr;
int i;
/* set up an empty fifo */
fifo_q->head = 0;
fifo_q->tail = 0;
fifo_q->reset = 0;
fifo_q->nrents = nr_entry;
fifo_q->imask = nr_entry - 1;
fifo_q->merge = ENTRY_MASK_O;
for (i = 0; i < nr_entry; i++)
fifo_q->fifobar[i] = 0;
}
static void ilo_ccb_close(struct pci_dev *pdev, struct ccb_data *data)
{
struct ccb *driver_ccb;
struct ccb __iomem *device_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),
&device_ccb->send_ctrl);
iowrite32(ioread32(&device_ccb->recv_ctrl) & ~(1 << CTRL_BITPOS_G),
&device_ccb->recv_ctrl);
/* give iLO some time to process stop request */
for (retries = MAX_WAIT; retries > 0; retries--) {
doorbell_set(driver_ccb);
udelay(WAIT_TIME);
if (!(ioread32(&device_ccb->send_ctrl) & (1 << CTRL_BITPOS_A))
&&
!(ioread32(&device_ccb->recv_ctrl) & (1 << CTRL_BITPOS_A)))
break;
}
if (retries == 0)
dev_err(&pdev->dev, "Closing, but controller still active\n");
/* clear the hw ccb */
memset_io(device_ccb, 0, sizeof(struct ccb));
/* free resources used to back send/recv queues */
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)
{
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_pa);
if (!data->dma_va)
goto out;
dma_va = (char *)data->dma_va;
dma_pa = (char *)data->dma_pa;
memset(dma_va, 0, data->dma_size);
dma_va = (char *)roundup((unsigned long)dma_va, ILO_START_ALIGN);
dma_pa = (char *)roundup((unsigned long)dma_pa, ILO_START_ALIGN);
/*
* Create two ccb's, one with virt addrs, one with phys addrs.
* Copy the phys addr ccb to device shared mem.
*/
ctrl_setup(driver_ccb, NR_QENTRY, L2_QENTRY_SZ);
ctrl_setup(ilo_ccb, NR_QENTRY, L2_QENTRY_SZ);
fifo_setup(dma_va, NR_QENTRY);
driver_ccb->ccb_u1.send_fifobar = dma_va + FIFOHANDLESIZE;
ilo_ccb->ccb_u1.send_fifobar = dma_pa + FIFOHANDLESIZE;
dma_va += fifo_sz(NR_QENTRY);
dma_pa += fifo_sz(NR_QENTRY);
dma_va = (char *)roundup((unsigned long)dma_va, ILO_CACHE_SZ);
dma_pa = (char *)roundup((unsigned long)dma_pa, ILO_CACHE_SZ);
fifo_setup(dma_va, NR_QENTRY);
driver_ccb->ccb_u3.recv_fifobar = dma_va + FIFOHANDLESIZE;
ilo_ccb->ccb_u3.recv_fifobar = dma_pa + FIFOHANDLESIZE;
dma_va += fifo_sz(NR_QENTRY);
dma_pa += fifo_sz(NR_QENTRY);
driver_ccb->ccb_u2.send_desc = dma_va;
ilo_ccb->ccb_u2.send_desc = dma_pa;
dma_pa += desc_mem_sz(NR_QENTRY);
dma_va += desc_mem_sz(NR_QENTRY);
driver_ccb->ccb_u4.recv_desc = dma_va;
ilo_ccb->ccb_u4.recv_desc = dma_pa;
driver_ccb->channel = slot;
ilo_ccb->channel = 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 */
/* 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));
/* put packets on the send and receive queues */
pkt_sz = 0;
for (pkt_id = 0; pkt_id < NR_QENTRY; pkt_id++) {
ilo_pkt_enqueue(hw, driver_ccb, SENDQ, pkt_id, pkt_sz);
doorbell_set(driver_ccb);
}
pkt_sz = desc_mem_sz(1);
for (pkt_id = 0; pkt_id < NR_QENTRY; pkt_id++)
ilo_pkt_enqueue(hw, driver_ccb, RECVQ, pkt_id, pkt_sz);
doorbell_clr(driver_ccb);
/* make sure iLO is really handling requests */
for (i = MAX_WAIT; i > 0; i--) {
if (ilo_pkt_dequeue(hw, driver_ccb, SENDQ, &pkt_id, NULL, NULL))
break;
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;
}
return 0;
free:
ilo_ccb_close(pdev, data);
out:
return error;
}
static inline int is_channel_reset(struct ccb *ccb)
{
/* check for this particular channel needing a reset */
return FIFOBARTOHANDLE(ccb->ccb_u1.send_fifobar)->reset;
}
static inline void set_channel_reset(struct ccb *ccb)
{
/* set a flag indicating this channel needs a reset */
FIFOBARTOHANDLE(ccb->ccb_u1.send_fifobar)->reset = 1;
}
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);
}
static inline void clear_device(struct ilo_hwinfo *hw)
{
/* clear the device (reset bits, pending channel entries) */
iowrite32(-1, &hw->mmio_vaddr[DB_OUT]);
}
static void ilo_locked_reset(struct ilo_hwinfo *hw)
{
int slot;
/*
* Mapped memory is zeroed on ilo reset, so set a per ccb flag
* to indicate that this ccb needs to be closed and reopened.
*/
for (slot = 0; slot < MAX_CCB; slot++) {
if (!hw->ccb_alloc[slot])
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;
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);
return -ENODEV;
}
/*
* This function is to be called when data is expected
* in the channel, and will return an error if no packet is found
* during the loop below. The sleep/retry logic is to allow
* applications to call read() immediately post write(),
* and give iLO some time to process the sent packet.
*/
cnt = 20;
do {
/* look for a received packet */
found = ilo_pkt_dequeue(hw, driver_ccb, RECVQ, &pkt_id,
&pkt_len, &pkt);
if (found)
break;
cnt--;
msleep(100);
} while (!found && cnt);
if (!found)
return -EAGAIN;
/* only copy the length of the received packet */
if (pkt_len < len)
len = pkt_len;
err = copy_to_user(buf, pkt, len);
/* return the received packet to the queue */
ilo_pkt_enqueue(hw, driver_ccb, RECVQ, pkt_id, desc_mem_sz(1));
return err ? -EFAULT : len;
}
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;
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);
return -ENODEV;
}
/* get a packet to send the user command */
if (!ilo_pkt_dequeue(hw, driver_ccb, SENDQ, &pkt_id, &pkt_len, &pkt))
return -EBUSY;
/* limit the length to the length of the packet */
if (pkt_len < len)
len = pkt_len;
/* on failure, set the len to 0 to return empty packet to the device */
err = copy_from_user(pkt, buf, len);
if (err)
len = 0;
/* send the packet */
ilo_pkt_enqueue(hw, driver_ccb, SENDQ, pkt_id, len);
doorbell_set(driver_ccb);
return err ? -EFAULT : len;
}
static int ilo_close(struct inode *ip, struct file *fp)
{
int slot;
struct ccb_data *data;
struct ilo_hwinfo *hw;
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);
if (hw->ccb_alloc[slot]->ccb_cnt == 1) {
data = fp->private_data;
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);
return 0;
}
static int ilo_open(struct inode *ip, struct file *fp)
{
int slot, error;
struct ccb_data *data;
struct ilo_hwinfo *hw;
slot = iminor(ip) % MAX_CCB;
hw = container_of(ip->i_cdev, struct ilo_hwinfo, cdev);
/* new ccb allocation */
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
spin_lock(&hw->alloc_lock);
if (is_device_reset(hw))
ilo_locked_reset(hw);
/* 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
kfree(data);
} else {
kfree(data);
if (fp->f_flags & O_EXCL || hw->ccb_alloc[slot]->ccb_excl) {
/*
* The channel exists, and either this open
* or a previous open of this channel wants
* exclusive access.
*/
error = -EBUSY;
} else {
hw->ccb_alloc[slot]->ccb_cnt++;
error = 0;
}
}
spin_unlock(&hw->alloc_lock);
if (!error)
fp->private_data = hw->ccb_alloc[slot];
return error;
}
static const struct file_operations ilo_fops = {
.owner = THIS_MODULE,
.read = ilo_read,
.write = ilo_write,
.open = ilo_open,
.release = ilo_close,
};
static void ilo_unmap_device(struct pci_dev *pdev, struct ilo_hwinfo *hw)
{
pci_iounmap(pdev, hw->db_vaddr);
pci_iounmap(pdev, hw->ram_vaddr);
pci_iounmap(pdev, hw->mmio_vaddr);
}
static int __devinit ilo_map_device(struct pci_dev *pdev, struct ilo_hwinfo *hw)
{
int error = -ENOMEM;
/* map the memory mapped i/o registers */
hw->mmio_vaddr = pci_iomap(pdev, 1, 0);
if (hw->mmio_vaddr == NULL) {
dev_err(&pdev->dev, "Error mapping mmio\n");
goto out;
}
/* map the adapter shared memory region */
hw->ram_vaddr = pci_iomap(pdev, 2, MAX_CCB * ILOHW_CCB_SZ);
if (hw->ram_vaddr == NULL) {
dev_err(&pdev->dev, "Error mapping shared mem\n");
goto mmio_free;
}
/* map the doorbell aperture */
hw->db_vaddr = pci_iomap(pdev, 3, MAX_CCB * ONE_DB_SIZE);
if (hw->db_vaddr == NULL) {
dev_err(&pdev->dev, "Error mapping doorbell\n");
goto ram_free;
}
return 0;
ram_free:
pci_iounmap(pdev, hw->ram_vaddr);
mmio_free:
pci_iounmap(pdev, hw->mmio_vaddr);
out:
return error;
}
static void ilo_remove(struct pci_dev *pdev)
{
int i, minor;
struct ilo_hwinfo *ilo_hw = pci_get_drvdata(pdev);
clear_device(ilo_hw);
minor = MINOR(ilo_hw->cdev.dev);
for (i = minor; i < minor + MAX_CCB; i++)
device_destroy(ilo_class, MKDEV(ilo_major, i));
cdev_del(&ilo_hw->cdev);
ilo_unmap_device(pdev, ilo_hw);
pci_release_regions(pdev);
pci_disable_device(pdev);
kfree(ilo_hw);
ilo_hwdev[(minor / MAX_CCB)] = 0;
}
static int __devinit ilo_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
int devnum, minor, start, error;
struct ilo_hwinfo *ilo_hw;
/* find a free range for device files */
for (devnum = 0; devnum < MAX_ILO_DEV; devnum++) {
if (ilo_hwdev[devnum] == 0) {
ilo_hwdev[devnum] = 1;
break;
}
}
if (devnum == MAX_ILO_DEV) {
dev_err(&pdev->dev, "Error finding free device\n");
return -ENODEV;
}
/* track global allocations for this device */
error = -ENOMEM;
ilo_hw = kzalloc(sizeof(*ilo_hw), GFP_KERNEL);
if (!ilo_hw)
goto out;
ilo_hw->ilo_dev = pdev;
spin_lock_init(&ilo_hw->alloc_lock);
spin_lock_init(&ilo_hw->fifo_lock);
error = pci_enable_device(pdev);
if (error)
goto free;
pci_set_master(pdev);
error = pci_request_regions(pdev, ILO_NAME);
if (error)
goto disable;
error = ilo_map_device(pdev, ilo_hw);
if (error)
goto free_regions;
pci_set_drvdata(pdev, ilo_hw);
clear_device(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;
}
for (minor = 0 ; minor < MAX_CCB; minor++) {
struct device *dev;
dev = device_create(ilo_class, &pdev->dev,
MKDEV(ilo_major, minor), NULL,
"hpilo!d%dccb%d", devnum, minor);
if (IS_ERR(dev))
dev_err(&pdev->dev, "Could not create files\n");
}
return 0;
unmap:
ilo_unmap_device(pdev, ilo_hw);
free_regions:
pci_release_regions(pdev);
disable:
pci_disable_device(pdev);
free:
kfree(ilo_hw);
out:
ilo_hwdev[devnum] = 0;
return error;
}
static struct pci_device_id ilo_devices[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB204) },
{ PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3307) },
{ }
};
MODULE_DEVICE_TABLE(pci, ilo_devices);
static struct pci_driver ilo_driver = {
.name = ILO_NAME,
.id_table = ilo_devices,
.probe = ilo_probe,
.remove = __devexit_p(ilo_remove),
};
static int __init ilo_init(void)
{
int error;
dev_t dev;
ilo_class = class_create(THIS_MODULE, "iLO");
if (IS_ERR(ilo_class)) {
error = PTR_ERR(ilo_class);
goto out;
}
error = alloc_chrdev_region(&dev, 0, MAX_OPEN, ILO_NAME);
if (error)
goto class_destroy;
ilo_major = MAJOR(dev);
error = pci_register_driver(&ilo_driver);
if (error)
goto chr_remove;
return 0;
chr_remove:
unregister_chrdev_region(dev, MAX_OPEN);
class_destroy:
class_destroy(ilo_class);
out:
return error;
}
static void __exit ilo_exit(void)
{
pci_unregister_driver(&ilo_driver);
unregister_chrdev_region(MKDEV(ilo_major, 0), MAX_OPEN);
class_destroy(ilo_class);
}
MODULE_VERSION("1.1");
MODULE_ALIAS(ILO_NAME);
MODULE_DESCRIPTION(ILO_NAME);
MODULE_AUTHOR("David Altobelli <david.altobelli@hp.com>");
MODULE_LICENSE("GPL v2");
module_init(ilo_init);
module_exit(ilo_exit);