linux-sg2042/drivers/misc/hpilo.c

934 lines
22 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Driver for the HP iLO management processor.
*
* Copyright (C) 2008 Hewlett-Packard Development Company, L.P.
* David Altobelli <david.altobelli@hpe.com>
*/
#include <linux/kernel.h>
#include <linux/types.h>
#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 cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
#include <linux/slab.h>
#include "hpilo.h"
static struct class *ilo_class;
static unsigned int ilo_major;
static unsigned int max_ccb = 16;
static char ilo_hwdev[MAX_ILO_DEV];
static const struct pci_device_id ilo_blacklist[] = {
/* auxiliary iLO */
{PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3307, PCI_VENDOR_ID_HP, 0x1979)},
/* CL */
{PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3307, PCI_VENDOR_ID_HP_3PAR, 0x0289)},
{}
};
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);
unsigned long flags;
int ret = 0;
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] |=
(entry & ENTRY_MASK_NOSTATE) | fifo_q->merge;
fifo_q->tail += 1;
ret = 1;
}
spin_unlock_irqrestore(&hw->fifo_lock, flags);
return ret;
}
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_irqsave(&hw->fifo_lock, flags);
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_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;
}
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 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);
}
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 */
hpilo: Replace one-element array with flexible-array member There is a regular need in the kernel to provide a way to declare having a dynamically sized set of trailing elements in a structure. Kernel code should always use “flexible array members”[1] for these cases. The older style of one-element or zero-length arrays should no longer be used[2]. For this particular case, it is important to notice that the cachelines change from 7 to 6 after the flexible-array conversion: $ pahole -C 'fifo' drivers/misc/hpilo.o struct fifo { u64 nrents; /* 0 8 */ u64 imask; /* 8 8 */ u64 merge; /* 16 8 */ u64 reset; /* 24 8 */ u8 pad_0[96]; /* 32 96 */ /* --- cacheline 2 boundary (128 bytes) --- */ u64 head; /* 128 8 */ u8 pad_1[120]; /* 136 120 */ /* --- cacheline 4 boundary (256 bytes) --- */ u64 tail; /* 256 8 */ u8 pad_2[120]; /* 264 120 */ /* --- cacheline 6 boundary (384 bytes) --- */ u64 fifobar[1]; /* 384 8 */ /* size: 392, cachelines: 7, members: 10 */ /* last cacheline: 8 bytes */ }; $ pahole -C 'fifo' drivers/misc/hpilo.o struct fifo { u64 nrents; /* 0 8 */ u64 imask; /* 8 8 */ u64 merge; /* 16 8 */ u64 reset; /* 24 8 */ u8 pad_0[96]; /* 32 96 */ /* --- cacheline 2 boundary (128 bytes) --- */ u64 head; /* 128 8 */ u8 pad_1[120]; /* 136 120 */ /* --- cacheline 4 boundary (256 bytes) --- */ u64 tail; /* 256 8 */ u8 pad_2[120]; /* 264 120 */ /* --- cacheline 6 boundary (384 bytes) --- */ u64 fifobar[]; /* 384 0 */ /* size: 384, cachelines: 6, members: 10 */ }; Lastly, remove unnecessary parentheses in fifo_sz() and fix the following checkpatch.pl warning for the whole fifo structure: WARNING: please, no spaces at the start of a line [1] https://en.wikipedia.org/wiki/Flexible_array_member [2] https://github.com/KSPP/linux/issues/79 Tested-by: kernel test robot <lkp@intel.com> Link: https://github.com/GustavoARSilva/linux-hardening/blob/master/cii/kernel-ci/hpilo-20200714.md Signed-off-by: Gustavo A. R. Silva <gustavoars@kernel.org> Link: https://lore.kernel.org/r/20200714154449.GA26153@embeddedor Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-07-14 23:44:49 +08:00
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 = &data->driver_ccb;
struct ccb __iomem *device_ccb = data->mapped_ccb;
int retries;
/* 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 */
misc: hpilo: switch from 'pci_' to 'dma_' API The wrappers in include/linux/pci-dma-compat.h should go away. The patch has been generated with the coccinelle script below and has been hand modified to replace GFP_ with a correct flag. It has been compile tested. When memory is allocated in 'ilo_ccb_setup()' GFP_ATOMIC must be used because a spin_lock is hold in 'ilo_open()' before calling 'ilo_ccb_setup()' @@ @@ - PCI_DMA_BIDIRECTIONAL + DMA_BIDIRECTIONAL @@ @@ - PCI_DMA_TODEVICE + DMA_TO_DEVICE @@ @@ - PCI_DMA_FROMDEVICE + DMA_FROM_DEVICE @@ @@ - PCI_DMA_NONE + DMA_NONE @@ expression e1, e2, e3; @@ - pci_alloc_consistent(e1, e2, e3) + dma_alloc_coherent(&e1->dev, e2, e3, GFP_) @@ expression e1, e2, e3; @@ - pci_zalloc_consistent(e1, e2, e3) + dma_alloc_coherent(&e1->dev, e2, e3, GFP_) @@ expression e1, e2, e3, e4; @@ - pci_free_consistent(e1, e2, e3, e4) + dma_free_coherent(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_map_single(e1, e2, e3, e4) + dma_map_single(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_unmap_single(e1, e2, e3, e4) + dma_unmap_single(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4, e5; @@ - pci_map_page(e1, e2, e3, e4, e5) + dma_map_page(&e1->dev, e2, e3, e4, e5) @@ expression e1, e2, e3, e4; @@ - pci_unmap_page(e1, e2, e3, e4) + dma_unmap_page(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_map_sg(e1, e2, e3, e4) + dma_map_sg(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_unmap_sg(e1, e2, e3, e4) + dma_unmap_sg(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_dma_sync_single_for_cpu(e1, e2, e3, e4) + dma_sync_single_for_cpu(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_dma_sync_single_for_device(e1, e2, e3, e4) + dma_sync_single_for_device(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_dma_sync_sg_for_cpu(e1, e2, e3, e4) + dma_sync_sg_for_cpu(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_dma_sync_sg_for_device(e1, e2, e3, e4) + dma_sync_sg_for_device(&e1->dev, e2, e3, e4) @@ expression e1, e2; @@ - pci_dma_mapping_error(e1, e2) + dma_mapping_error(&e1->dev, e2) @@ expression e1, e2; @@ - pci_set_dma_mask(e1, e2) + dma_set_mask(&e1->dev, e2) @@ expression e1, e2; @@ - pci_set_consistent_dma_mask(e1, e2) + dma_set_coherent_mask(&e1->dev, e2) Signed-off-by: Christophe JAILLET <christophe.jaillet@wanadoo.fr> Link: https://lore.kernel.org/r/20200718070224.337964-1-christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-07-18 15:02:24 +08:00
dma_free_coherent(&pdev->dev, data->dma_size, data->dma_va,
data->dma_pa);
}
static int ilo_ccb_setup(struct ilo_hwinfo *hw, struct ccb_data *data, int slot)
{
char *dma_va;
dma_addr_t dma_pa;
struct ccb *driver_ccb, *ilo_ccb;
driver_ccb = &data->driver_ccb;
ilo_ccb = &data->ilo_ccb;
data->dma_size = 2 * fifo_sz(NR_QENTRY) +
2 * desc_mem_sz(NR_QENTRY) +
ILO_START_ALIGN + ILO_CACHE_SZ;
misc: hpilo: switch from 'pci_' to 'dma_' API The wrappers in include/linux/pci-dma-compat.h should go away. The patch has been generated with the coccinelle script below and has been hand modified to replace GFP_ with a correct flag. It has been compile tested. When memory is allocated in 'ilo_ccb_setup()' GFP_ATOMIC must be used because a spin_lock is hold in 'ilo_open()' before calling 'ilo_ccb_setup()' @@ @@ - PCI_DMA_BIDIRECTIONAL + DMA_BIDIRECTIONAL @@ @@ - PCI_DMA_TODEVICE + DMA_TO_DEVICE @@ @@ - PCI_DMA_FROMDEVICE + DMA_FROM_DEVICE @@ @@ - PCI_DMA_NONE + DMA_NONE @@ expression e1, e2, e3; @@ - pci_alloc_consistent(e1, e2, e3) + dma_alloc_coherent(&e1->dev, e2, e3, GFP_) @@ expression e1, e2, e3; @@ - pci_zalloc_consistent(e1, e2, e3) + dma_alloc_coherent(&e1->dev, e2, e3, GFP_) @@ expression e1, e2, e3, e4; @@ - pci_free_consistent(e1, e2, e3, e4) + dma_free_coherent(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_map_single(e1, e2, e3, e4) + dma_map_single(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_unmap_single(e1, e2, e3, e4) + dma_unmap_single(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4, e5; @@ - pci_map_page(e1, e2, e3, e4, e5) + dma_map_page(&e1->dev, e2, e3, e4, e5) @@ expression e1, e2, e3, e4; @@ - pci_unmap_page(e1, e2, e3, e4) + dma_unmap_page(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_map_sg(e1, e2, e3, e4) + dma_map_sg(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_unmap_sg(e1, e2, e3, e4) + dma_unmap_sg(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_dma_sync_single_for_cpu(e1, e2, e3, e4) + dma_sync_single_for_cpu(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_dma_sync_single_for_device(e1, e2, e3, e4) + dma_sync_single_for_device(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_dma_sync_sg_for_cpu(e1, e2, e3, e4) + dma_sync_sg_for_cpu(&e1->dev, e2, e3, e4) @@ expression e1, e2, e3, e4; @@ - pci_dma_sync_sg_for_device(e1, e2, e3, e4) + dma_sync_sg_for_device(&e1->dev, e2, e3, e4) @@ expression e1, e2; @@ - pci_dma_mapping_error(e1, e2) + dma_mapping_error(&e1->dev, e2) @@ expression e1, e2; @@ - pci_set_dma_mask(e1, e2) + dma_set_mask(&e1->dev, e2) @@ expression e1, e2; @@ - pci_set_consistent_dma_mask(e1, e2) + dma_set_coherent_mask(&e1->dev, e2) Signed-off-by: Christophe JAILLET <christophe.jaillet@wanadoo.fr> Link: https://lore.kernel.org/r/20200718070224.337964-1-christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-07-18 15:02:24 +08:00
data->dma_va = dma_alloc_coherent(&hw->ilo_dev->dev, data->dma_size,
&data->dma_pa, GFP_ATOMIC);
if (!data->dma_va)
return -ENOMEM;
dma_va = (char *)data->dma_va;
dma_pa = data->dma_pa;
dma_va = (char *)roundup((unsigned long)dma_va, ILO_START_ALIGN);
dma_pa = roundup(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_pa = 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 = roundup(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_pa = 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_pa = 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_pa = 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 */
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, &data->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);
/* 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--) {
if (ilo_pkt_dequeue(hw, driver_ccb, SENDQ, &pkt_id, NULL, NULL))
break;
udelay(WAIT_TIME);
}
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;
}
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 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 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) */
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 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;
/*
* 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);
}
}
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 = fp->private_data;
struct ccb *driver_ccb = &data->driver_ccb;
struct ilo_hwinfo *hw = data->ilo_hw;
void *pkt;
if (is_channel_reset(driver_ccb)) {
/*
* If the device has been reset, applications
* need to close and reopen all ccbs.
*/
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 = fp->private_data;
struct ccb *driver_ccb = &data->driver_ccb;
struct ilo_hwinfo *hw = data->ilo_hw;
void *pkt;
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))
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 __poll_t 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 EPOLLERR;
else if (ilo_pkt_recv(data->ilo_hw, driver_ccb))
return EPOLLIN | EPOLLRDNORM;
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->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);
} else
hw->ccb_alloc[slot]->ccb_cnt--;
spin_unlock(&hw->open_lock);
return 0;
}
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);
/* new ccb allocation */
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
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_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) {
/*
* 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;
}
}
out:
spin_unlock(&hw->open_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,
.poll = ilo_poll,
.open = ilo_open,
.release = ilo_close,
llseek: automatically add .llseek fop All file_operations should get a .llseek operation so we can make nonseekable_open the default for future file operations without a .llseek pointer. The three cases that we can automatically detect are no_llseek, seq_lseek and default_llseek. For cases where we can we can automatically prove that the file offset is always ignored, we use noop_llseek, which maintains the current behavior of not returning an error from a seek. New drivers should normally not use noop_llseek but instead use no_llseek and call nonseekable_open at open time. Existing drivers can be converted to do the same when the maintainer knows for certain that no user code relies on calling seek on the device file. The generated code is often incorrectly indented and right now contains comments that clarify for each added line why a specific variant was chosen. In the version that gets submitted upstream, the comments will be gone and I will manually fix the indentation, because there does not seem to be a way to do that using coccinelle. Some amount of new code is currently sitting in linux-next that should get the same modifications, which I will do at the end of the merge window. Many thanks to Julia Lawall for helping me learn to write a semantic patch that does all this. ===== begin semantic patch ===== // This adds an llseek= method to all file operations, // as a preparation for making no_llseek the default. // // The rules are // - use no_llseek explicitly if we do nonseekable_open // - use seq_lseek for sequential files // - use default_llseek if we know we access f_pos // - use noop_llseek if we know we don't access f_pos, // but we still want to allow users to call lseek // @ open1 exists @ identifier nested_open; @@ nested_open(...) { <+... nonseekable_open(...) ...+> } @ open exists@ identifier open_f; identifier i, f; identifier open1.nested_open; @@ int open_f(struct inode *i, struct file *f) { <+... ( nonseekable_open(...) | nested_open(...) ) ...+> } @ read disable optional_qualifier exists @ identifier read_f; identifier f, p, s, off; type ssize_t, size_t, loff_t; expression E; identifier func; @@ ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off) { <+... ( *off = E | *off += E | func(..., off, ...) | E = *off ) ...+> } @ read_no_fpos disable optional_qualifier exists @ identifier read_f; identifier f, p, s, off; type ssize_t, size_t, loff_t; @@ ssize_t read_f(struct file *f, char *p, size_t s, loff_t *off) { ... when != off } @ write @ identifier write_f; identifier f, p, s, off; type ssize_t, size_t, loff_t; expression E; identifier func; @@ ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off) { <+... ( *off = E | *off += E | func(..., off, ...) | E = *off ) ...+> } @ write_no_fpos @ identifier write_f; identifier f, p, s, off; type ssize_t, size_t, loff_t; @@ ssize_t write_f(struct file *f, const char *p, size_t s, loff_t *off) { ... when != off } @ fops0 @ identifier fops; @@ struct file_operations fops = { ... }; @ has_llseek depends on fops0 @ identifier fops0.fops; identifier llseek_f; @@ struct file_operations fops = { ... .llseek = llseek_f, ... }; @ has_read depends on fops0 @ identifier fops0.fops; identifier read_f; @@ struct file_operations fops = { ... .read = read_f, ... }; @ has_write depends on fops0 @ identifier fops0.fops; identifier write_f; @@ struct file_operations fops = { ... .write = write_f, ... }; @ has_open depends on fops0 @ identifier fops0.fops; identifier open_f; @@ struct file_operations fops = { ... .open = open_f, ... }; // use no_llseek if we call nonseekable_open //////////////////////////////////////////// @ nonseekable1 depends on !has_llseek && has_open @ identifier fops0.fops; identifier nso ~= "nonseekable_open"; @@ struct file_operations fops = { ... .open = nso, ... +.llseek = no_llseek, /* nonseekable */ }; @ nonseekable2 depends on !has_llseek @ identifier fops0.fops; identifier open.open_f; @@ struct file_operations fops = { ... .open = open_f, ... +.llseek = no_llseek, /* open uses nonseekable */ }; // use seq_lseek for sequential files ///////////////////////////////////// @ seq depends on !has_llseek @ identifier fops0.fops; identifier sr ~= "seq_read"; @@ struct file_operations fops = { ... .read = sr, ... +.llseek = seq_lseek, /* we have seq_read */ }; // use default_llseek if there is a readdir /////////////////////////////////////////// @ fops1 depends on !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier readdir_e; @@ // any other fop is used that changes pos struct file_operations fops = { ... .readdir = readdir_e, ... +.llseek = default_llseek, /* readdir is present */ }; // use default_llseek if at least one of read/write touches f_pos ///////////////////////////////////////////////////////////////// @ fops2 depends on !fops1 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier read.read_f; @@ // read fops use offset struct file_operations fops = { ... .read = read_f, ... +.llseek = default_llseek, /* read accesses f_pos */ }; @ fops3 depends on !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier write.write_f; @@ // write fops use offset struct file_operations fops = { ... .write = write_f, ... + .llseek = default_llseek, /* write accesses f_pos */ }; // Use noop_llseek if neither read nor write accesses f_pos /////////////////////////////////////////////////////////// @ fops4 depends on !fops1 && !fops2 && !fops3 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier read_no_fpos.read_f; identifier write_no_fpos.write_f; @@ // write fops use offset struct file_operations fops = { ... .write = write_f, .read = read_f, ... +.llseek = noop_llseek, /* read and write both use no f_pos */ }; @ depends on has_write && !has_read && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier write_no_fpos.write_f; @@ struct file_operations fops = { ... .write = write_f, ... +.llseek = noop_llseek, /* write uses no f_pos */ }; @ depends on has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; identifier read_no_fpos.read_f; @@ struct file_operations fops = { ... .read = read_f, ... +.llseek = noop_llseek, /* read uses no f_pos */ }; @ depends on !has_read && !has_write && !fops1 && !fops2 && !has_llseek && !nonseekable1 && !nonseekable2 && !seq @ identifier fops0.fops; @@ struct file_operations fops = { ... +.llseek = noop_llseek, /* no read or write fn */ }; ===== End semantic patch ===== Signed-off-by: Arnd Bergmann <arnd@arndb.de> Cc: Julia Lawall <julia@diku.dk> Cc: Christoph Hellwig <hch@infradead.org>
2010-08-16 00:52:59 +08:00
.llseek = noop_llseek,
};
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);
pci_iounmap(pdev, hw->ram_vaddr);
pci_iounmap(pdev, hw->mmio_vaddr);
}
static int ilo_map_device(struct pci_dev *pdev, struct ilo_hwinfo *hw)
{
int bar;
unsigned long off;
u8 pci_rev_id;
int rc;
/* 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 */
rc = pci_read_config_byte(pdev, PCI_REVISION_ID, &pci_rev_id);
if (rc != 0) {
dev_err(&pdev->dev, "Error reading PCI rev id: %d\n", rc);
goto out;
}
if (pci_rev_id >= PCI_REV_ID_NECHES) {
bar = 5;
/* Last 8k is reserved for CCBs */
off = pci_resource_len(pdev, bar) - 0x2000;
} else {
bar = 2;
off = 0;
}
hw->ram_vaddr = pci_iomap_range(pdev, bar, off, 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 -ENOMEM;
}
static void ilo_remove(struct pci_dev *pdev)
{
int i, minor;
struct ilo_hwinfo *ilo_hw = pci_get_drvdata(pdev);
if (!ilo_hw)
return;
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_disable_interrupts(ilo_hw);
free_irq(pdev->irq, ilo_hw);
ilo_unmap_device(pdev, ilo_hw);
pci_release_regions(pdev);
/*
* pci_disable_device(pdev) used to be here. But this PCI device has
* two functions with interrupt lines connected to a single pin. The
* other one is a USB host controller. So when we disable the PIN here
* e.g. by rmmod hpilo, the controller stops working. It is because
* the interrupt link is disabled in ACPI since it is not refcounted
* yet. See acpi_pci_link_free_irq called from acpi_pci_irq_disable.
*/
kfree(ilo_hw);
ilo_hwdev[(minor / max_ccb)] = 0;
}
static int ilo_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
{
int devnum, minor, start, error = 0;
struct ilo_hwinfo *ilo_hw;
if (pci_match_id(ilo_blacklist, pdev)) {
dev_dbg(&pdev->dev, "Not supported on this device\n");
return -ENODEV;
}
if (max_ccb > MAX_CCB)
max_ccb = MAX_CCB;
else if (max_ccb < MIN_CCB)
max_ccb = MIN_CCB;
/* 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);
spin_lock_init(&ilo_hw->open_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);
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 remove_isr;
}
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;
remove_isr:
ilo_disable_interrupts(ilo_hw);
free_irq(pdev->irq, ilo_hw);
unmap:
ilo_unmap_device(pdev, ilo_hw);
free_regions:
pci_release_regions(pdev);
disable:
/* pci_disable_device(pdev); see comment in ilo_remove */
free:
kfree(ilo_hw);
out:
ilo_hwdev[devnum] = 0;
return error;
}
static const 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 = 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.5.0");
MODULE_ALIAS(ILO_NAME);
MODULE_DESCRIPTION(ILO_NAME);
MODULE_AUTHOR("David Altobelli <david.altobelli@hpe.com>");
MODULE_LICENSE("GPL v2");
module_param(max_ccb, uint, 0444);
MODULE_PARM_DESC(max_ccb, "Maximum number of HP iLO channels to attach (8-24)(default=16)");
module_init(ilo_init);
module_exit(ilo_exit);