2017-12-07 12:31:48 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2008-07-02 22:38:53 +07:00
|
|
|
/*
|
2010-10-15 23:14:34 +07:00
|
|
|
* Driver for the HP iLO management processor.
|
2008-07-02 22:38:53 +07:00
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Hewlett-Packard Development Company, L.P.
|
2015-09-18 10:32:13 +07:00
|
|
|
* David Altobelli <david.altobelli@hpe.com>
|
2008-07-02 22:38:53 +07:00
|
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/fs.h>
|
|
|
|
#include <linux/pci.h>
|
2009-08-18 06:07:33 +07:00
|
|
|
#include <linux/interrupt.h>
|
2008-07-02 22:38:53 +07:00
|
|
|
#include <linux/ioport.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/file.h>
|
|
|
|
#include <linux/cdev.h>
|
2009-10-07 20:09:06 +07:00
|
|
|
#include <linux/sched.h>
|
2008-07-02 22:38:53 +07:00
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <linux/io.h>
|
2009-08-18 06:07:33 +07:00
|
|
|
#include <linux/wait.h>
|
2009-08-18 06:08:01 +07:00
|
|
|
#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 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2008-07-02 22:38:53 +07:00
|
|
|
#include "hpilo.h"
|
|
|
|
|
|
|
|
static struct class *ilo_class;
|
|
|
|
static unsigned int ilo_major;
|
2012-11-07 03:33:13 +07:00
|
|
|
static unsigned int max_ccb = 16;
|
2008-07-02 22:38:53 +07:00
|
|
|
static char ilo_hwdev[MAX_ILO_DEV];
|
2019-02-27 10:25:29 +07:00
|
|
|
static const struct pci_device_id ilo_blacklist[] = {
|
|
|
|
/* auxiliary iLO */
|
|
|
|
{PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3307, PCI_VENDOR_ID_HP, 0x1979)},
|
2019-02-27 10:25:30 +07:00
|
|
|
/* CL */
|
|
|
|
{PCI_DEVICE_SUB(PCI_VENDOR_ID_HP, 0x3307, PCI_VENDOR_ID_HP_3PAR, 0x0289)},
|
2019-02-27 10:25:29 +07:00
|
|
|
{}
|
|
|
|
};
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
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);
|
2009-08-18 06:07:33 +07:00
|
|
|
unsigned long flags;
|
2008-07-02 22:38:53 +07:00
|
|
|
int ret = 0;
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_lock_irqsave(&hw->fifo_lock, flags);
|
2008-07-02 22:38:53 +07:00
|
|
|
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;
|
|
|
|
}
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_unlock_irqrestore(&hw->fifo_lock, flags);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int fifo_dequeue(struct ilo_hwinfo *hw, char *fifobar, int *entry)
|
|
|
|
{
|
|
|
|
struct fifo *fifo_q = FIFOBARTOHANDLE(fifobar);
|
2009-08-18 06:07:33 +07:00
|
|
|
unsigned long flags;
|
2008-07-02 22:38:53 +07:00
|
|
|
int ret = 0;
|
|
|
|
u64 c;
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_lock_irqsave(&hw->fifo_lock, flags);
|
2008-07-02 22:38:53 +07:00
|
|
|
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;
|
|
|
|
}
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_unlock_irqrestore(&hw->fifo_lock, flags);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-08-18 06:08:01 +07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-08-18 06:08:01 +07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
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);
|
|
|
|
}
|
2009-08-18 06:07:03 +07:00
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
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;
|
|
|
|
}
|
2009-08-18 06:07:03 +07:00
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
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 */
|
2020-07-14 22:44:49 +07:00
|
|
|
return nr_entry * sizeof(u64) + FIFOHANDLESIZE;
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2009-08-18 06:07:03 +07:00
|
|
|
struct ccb *driver_ccb = &data->driver_ccb;
|
|
|
|
struct ccb __iomem *device_ccb = data->mapped_ccb;
|
2008-07-02 22:38:53 +07:00
|
|
|
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 */
|
2009-02-05 06:11:58 +07:00
|
|
|
for (retries = MAX_WAIT; retries > 0; retries--) {
|
2008-07-02 22:38:53 +07:00
|
|
|
doorbell_set(driver_ccb);
|
2009-04-01 05:23:53 +07:00
|
|
|
udelay(WAIT_TIME);
|
2008-07-02 22:38:53 +07:00
|
|
|
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 14:02:24 +07:00
|
|
|
dma_free_coherent(&pdev->dev, data->dma_size, data->dma_va,
|
|
|
|
data->dma_pa);
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
static int ilo_ccb_setup(struct ilo_hwinfo *hw, struct ccb_data *data, int slot)
|
2008-07-02 22:38:53 +07:00
|
|
|
{
|
2010-08-10 07:20:27 +07:00
|
|
|
char *dma_va;
|
|
|
|
dma_addr_t dma_pa;
|
2008-07-02 22:38:53 +07:00
|
|
|
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 14:02:24 +07:00
|
|
|
data->dma_va = dma_alloc_coherent(&hw->ilo_dev->dev, data->dma_size,
|
|
|
|
&data->dma_pa, GFP_ATOMIC);
|
2008-07-02 22:38:53 +07:00
|
|
|
if (!data->dma_va)
|
2009-08-18 06:07:03 +07:00
|
|
|
return -ENOMEM;
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
dma_va = (char *)data->dma_va;
|
2010-08-10 07:20:27 +07:00
|
|
|
dma_pa = data->dma_pa;
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
dma_va = (char *)roundup((unsigned long)dma_va, ILO_START_ALIGN);
|
2010-08-10 07:20:27 +07:00
|
|
|
dma_pa = roundup(dma_pa, ILO_START_ALIGN);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
2010-08-10 07:20:27 +07:00
|
|
|
ilo_ccb->ccb_u1.send_fifobar_pa = dma_pa + FIFOHANDLESIZE;
|
2008-07-02 22:38:53 +07:00
|
|
|
dma_va += fifo_sz(NR_QENTRY);
|
|
|
|
dma_pa += fifo_sz(NR_QENTRY);
|
|
|
|
|
|
|
|
dma_va = (char *)roundup((unsigned long)dma_va, ILO_CACHE_SZ);
|
2010-08-10 07:20:27 +07:00
|
|
|
dma_pa = roundup(dma_pa, ILO_CACHE_SZ);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
fifo_setup(dma_va, NR_QENTRY);
|
|
|
|
driver_ccb->ccb_u3.recv_fifobar = dma_va + FIFOHANDLESIZE;
|
2010-08-10 07:20:27 +07:00
|
|
|
ilo_ccb->ccb_u3.recv_fifobar_pa = dma_pa + FIFOHANDLESIZE;
|
2008-07-02 22:38:53 +07:00
|
|
|
dma_va += fifo_sz(NR_QENTRY);
|
|
|
|
dma_pa += fifo_sz(NR_QENTRY);
|
|
|
|
|
|
|
|
driver_ccb->ccb_u2.send_desc = dma_va;
|
2010-08-10 07:20:27 +07:00
|
|
|
ilo_ccb->ccb_u2.send_desc_pa = dma_pa;
|
2008-07-02 22:38:53 +07:00
|
|
|
dma_pa += desc_mem_sz(NR_QENTRY);
|
|
|
|
dma_va += desc_mem_sz(NR_QENTRY);
|
|
|
|
|
|
|
|
driver_ccb->ccb_u4.recv_desc = dma_va;
|
2010-08-10 07:20:27 +07:00
|
|
|
ilo_ccb->ccb_u4.recv_desc_pa = dma_pa;
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
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 */
|
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
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;
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
/* copy the ccb with physical addrs to device memory */
|
|
|
|
data->mapped_ccb = (struct ccb __iomem *)
|
|
|
|
(hw->ram_vaddr + (slot * ILOHW_CCB_SZ));
|
2009-08-18 06:07:03 +07:00
|
|
|
memcpy_toio(data->mapped_ccb, &data->ilo_ccb, sizeof(struct ccb));
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
/* the ccb is ready to use */
|
2008-07-02 22:38:53 +07:00
|
|
|
doorbell_clr(driver_ccb);
|
2009-08-18 06:07:03 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ilo_ccb_verify(struct ilo_hwinfo *hw, struct ccb_data *data)
|
|
|
|
{
|
|
|
|
int pkt_id, i;
|
|
|
|
struct ccb *driver_ccb = &data->driver_ccb;
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
/* make sure iLO is really handling requests */
|
2009-02-05 06:11:58 +07:00
|
|
|
for (i = MAX_WAIT; i > 0; i--) {
|
2008-07-02 22:38:53 +07:00
|
|
|
if (ilo_pkt_dequeue(hw, driver_ccb, SENDQ, &pkt_id, NULL, NULL))
|
|
|
|
break;
|
2009-04-01 05:23:53 +07:00
|
|
|
udelay(WAIT_TIME);
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
if (i == 0) {
|
|
|
|
dev_err(&hw->ilo_dev->dev, "Open could not dequeue a packet\n");
|
|
|
|
return -EBUSY;
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
ilo_pkt_enqueue(hw, driver_ccb, SENDQ, pkt_id, 0);
|
|
|
|
doorbell_set(driver_ccb);
|
2008-07-02 22:38:53 +07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
static inline int is_device_reset(struct ilo_hwinfo *hw)
|
|
|
|
{
|
|
|
|
/* check for global reset condition */
|
2009-08-18 06:07:03 +07:00
|
|
|
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]);
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void clear_device(struct ilo_hwinfo *hw)
|
|
|
|
{
|
|
|
|
/* clear the device (reset bits, pending channel entries) */
|
2009-08-18 06:07:03 +07:00
|
|
|
clear_pending_db(hw, -1);
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
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)
|
2008-07-02 22:38:53 +07:00
|
|
|
{
|
|
|
|
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.
|
|
|
|
*/
|
2012-06-10 20:39:20 +07:00
|
|
|
for (slot = 0; slot < max_ccb; slot++) {
|
2008-07-02 22:38:53 +07:00
|
|
|
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;
|
2009-08-18 06:07:03 +07:00
|
|
|
struct ccb_data *data = fp->private_data;
|
|
|
|
struct ccb *driver_ccb = &data->driver_ccb;
|
|
|
|
struct ilo_hwinfo *hw = data->ilo_hw;
|
2008-07-02 22:38:53 +07:00
|
|
|
void *pkt;
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
if (is_channel_reset(driver_ccb)) {
|
2008-07-02 22:38:53 +07:00
|
|
|
/*
|
|
|
|
* 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;
|
2009-08-18 06:07:03 +07:00
|
|
|
struct ccb_data *data = fp->private_data;
|
|
|
|
struct ccb *driver_ccb = &data->driver_ccb;
|
|
|
|
struct ilo_hwinfo *hw = data->ilo_hw;
|
2008-07-02 22:38:53 +07:00
|
|
|
void *pkt;
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
if (is_channel_reset(driver_ccb))
|
2008-07-02 22:38:53 +07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-07-03 17:39:46 +07:00
|
|
|
static __poll_t ilo_poll(struct file *fp, poll_table *wait)
|
2009-08-18 06:08:01 +07:00
|
|
|
{
|
|
|
|
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))
|
2018-02-12 05:34:03 +07:00
|
|
|
return EPOLLERR;
|
2009-08-18 06:08:01 +07:00
|
|
|
else if (ilo_pkt_recv(data->ilo_hw, driver_ccb))
|
2018-02-12 05:34:03 +07:00
|
|
|
return EPOLLIN | EPOLLRDNORM;
|
2009-08-18 06:08:01 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
static int ilo_close(struct inode *ip, struct file *fp)
|
|
|
|
{
|
|
|
|
int slot;
|
|
|
|
struct ccb_data *data;
|
|
|
|
struct ilo_hwinfo *hw;
|
2009-08-18 06:07:33 +07:00
|
|
|
unsigned long flags;
|
2008-07-02 22:38:53 +07:00
|
|
|
|
2012-06-10 20:39:20 +07:00
|
|
|
slot = iminor(ip) % max_ccb;
|
2008-07-02 22:38:53 +07:00
|
|
|
hw = container_of(ip->i_cdev, struct ilo_hwinfo, cdev);
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_lock(&hw->open_lock);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
if (hw->ccb_alloc[slot]->ccb_cnt == 1) {
|
|
|
|
|
|
|
|
data = fp->private_data;
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_lock_irqsave(&hw->alloc_lock, flags);
|
|
|
|
hw->ccb_alloc[slot] = NULL;
|
|
|
|
spin_unlock_irqrestore(&hw->alloc_lock, flags);
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
ilo_ccb_close(hw->ilo_dev, data);
|
|
|
|
|
|
|
|
kfree(data);
|
|
|
|
} else
|
|
|
|
hw->ccb_alloc[slot]->ccb_cnt--;
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_unlock(&hw->open_lock);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ilo_open(struct inode *ip, struct file *fp)
|
|
|
|
{
|
|
|
|
int slot, error;
|
|
|
|
struct ccb_data *data;
|
|
|
|
struct ilo_hwinfo *hw;
|
2009-08-18 06:07:33 +07:00
|
|
|
unsigned long flags;
|
2008-07-02 22:38:53 +07:00
|
|
|
|
2012-06-10 20:39:20 +07:00
|
|
|
slot = iminor(ip) % max_ccb;
|
2008-07-02 22:38:53 +07:00
|
|
|
hw = container_of(ip->i_cdev, struct ilo_hwinfo, cdev);
|
|
|
|
|
|
|
|
/* new ccb allocation */
|
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
|
|
|
if (!data)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_lock(&hw->open_lock);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
/* each fd private_data holds sw/hw view of ccb */
|
|
|
|
if (hw->ccb_alloc[slot] == NULL) {
|
|
|
|
/* create a channel control block for this minor */
|
2009-08-18 06:07:03 +07:00
|
|
|
error = ilo_ccb_setup(hw, data, slot);
|
|
|
|
if (error) {
|
2008-07-02 22:38:53 +07:00
|
|
|
kfree(data);
|
2009-08-18 06:07:03 +07:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
data->ccb_cnt = 1;
|
|
|
|
data->ccb_excl = fp->f_flags & O_EXCL;
|
|
|
|
data->ilo_hw = hw;
|
|
|
|
init_waitqueue_head(&data->ccb_waitq);
|
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
/* write the ccb to hw */
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_lock_irqsave(&hw->alloc_lock, flags);
|
2009-08-18 06:07:03 +07:00
|
|
|
ilo_ccb_open(hw, data, slot);
|
2009-08-18 06:07:33 +07:00
|
|
|
hw->ccb_alloc[slot] = data;
|
|
|
|
spin_unlock_irqrestore(&hw->alloc_lock, flags);
|
2009-08-18 06:07:03 +07:00
|
|
|
|
|
|
|
/* make sure the channel is functional */
|
|
|
|
error = ilo_ccb_verify(hw, data);
|
|
|
|
if (error) {
|
2009-08-18 06:07:33 +07:00
|
|
|
|
|
|
|
spin_lock_irqsave(&hw->alloc_lock, flags);
|
|
|
|
hw->ccb_alloc[slot] = NULL;
|
|
|
|
spin_unlock_irqrestore(&hw->alloc_lock, flags);
|
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
ilo_ccb_close(hw->ilo_dev, data);
|
2009-08-18 06:07:33 +07:00
|
|
|
|
2009-08-18 06:07:03 +07:00
|
|
|
kfree(data);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
}
|
2009-08-18 06:07:03 +07:00
|
|
|
out:
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_unlock(&hw->open_lock);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
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,
|
2009-08-18 06:08:01 +07:00
|
|
|
.poll = ilo_poll,
|
2008-07-02 22:38:53 +07:00
|
|
|
.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-15 23:52:59 +07:00
|
|
|
.llseek = noop_llseek,
|
2008-07-02 22:38:53 +07:00
|
|
|
};
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2012-06-10 20:39:20 +07:00
|
|
|
for (i = 0; i < max_ccb; i++) {
|
2009-08-18 06:07:33 +07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2012-11-20 01:23:05 +07:00
|
|
|
static int ilo_map_device(struct pci_dev *pdev, struct ilo_hwinfo *hw)
|
2008-07-02 22:38:53 +07:00
|
|
|
{
|
2016-09-20 02:50:01 +07:00
|
|
|
int bar;
|
|
|
|
unsigned long off;
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
/* 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 */
|
2016-09-20 02:50:01 +07:00
|
|
|
if (pdev->subsystem_device == 0x00E4) {
|
|
|
|
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);
|
2008-07-02 22:38:53 +07:00
|
|
|
if (hw->ram_vaddr == NULL) {
|
|
|
|
dev_err(&pdev->dev, "Error mapping shared mem\n");
|
|
|
|
goto mmio_free;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* map the doorbell aperture */
|
2012-06-10 20:39:20 +07:00
|
|
|
hw->db_vaddr = pci_iomap(pdev, 3, max_ccb * ONE_DB_SIZE);
|
2008-07-02 22:38:53 +07:00
|
|
|
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:
|
2016-09-20 02:50:01 +07:00
|
|
|
return -ENOMEM;
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ilo_remove(struct pci_dev *pdev)
|
|
|
|
{
|
|
|
|
int i, minor;
|
|
|
|
struct ilo_hwinfo *ilo_hw = pci_get_drvdata(pdev);
|
|
|
|
|
2012-11-07 03:33:13 +07:00
|
|
|
if (!ilo_hw)
|
|
|
|
return;
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
clear_device(ilo_hw);
|
|
|
|
|
|
|
|
minor = MINOR(ilo_hw->cdev.dev);
|
2012-06-10 20:39:20 +07:00
|
|
|
for (i = minor; i < minor + max_ccb; i++)
|
2008-07-02 22:38:53 +07:00
|
|
|
device_destroy(ilo_class, MKDEV(ilo_major, i));
|
|
|
|
|
|
|
|
cdev_del(&ilo_hw->cdev);
|
2009-08-18 06:07:33 +07:00
|
|
|
ilo_disable_interrupts(ilo_hw);
|
|
|
|
free_irq(pdev->irq, ilo_hw);
|
2008-07-02 22:38:53 +07:00
|
|
|
ilo_unmap_device(pdev, ilo_hw);
|
|
|
|
pci_release_regions(pdev);
|
2012-09-13 21:06:48 +07:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2008-07-02 22:38:53 +07:00
|
|
|
kfree(ilo_hw);
|
2012-06-10 20:39:20 +07:00
|
|
|
ilo_hwdev[(minor / max_ccb)] = 0;
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
2012-11-20 01:23:05 +07:00
|
|
|
static int ilo_probe(struct pci_dev *pdev,
|
2008-07-02 22:38:53 +07:00
|
|
|
const struct pci_device_id *ent)
|
|
|
|
{
|
2012-11-07 03:33:13 +07:00
|
|
|
int devnum, minor, start, error = 0;
|
2008-07-02 22:38:53 +07:00
|
|
|
struct ilo_hwinfo *ilo_hw;
|
|
|
|
|
2019-02-27 10:25:29 +07:00
|
|
|
if (pci_match_id(ilo_blacklist, pdev)) {
|
|
|
|
dev_dbg(&pdev->dev, "Not supported on this device\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
2012-11-07 03:33:13 +07:00
|
|
|
|
2012-06-10 20:39:20 +07:00
|
|
|
if (max_ccb > MAX_CCB)
|
|
|
|
max_ccb = MAX_CCB;
|
|
|
|
else if (max_ccb < MIN_CCB)
|
|
|
|
max_ccb = MIN_CCB;
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
/* 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);
|
2009-08-18 06:07:33 +07:00
|
|
|
spin_lock_init(&ilo_hw->open_lock);
|
2008-07-02 22:38:53 +07:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2009-08-18 06:07:33 +07:00
|
|
|
error = request_irq(pdev->irq, ilo_isr, IRQF_SHARED, "hpilo", ilo_hw);
|
|
|
|
if (error)
|
|
|
|
goto unmap;
|
|
|
|
|
|
|
|
ilo_enable_interrupts(ilo_hw);
|
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
cdev_init(&ilo_hw->cdev, &ilo_fops);
|
|
|
|
ilo_hw->cdev.owner = THIS_MODULE;
|
2012-06-10 20:39:20 +07:00
|
|
|
start = devnum * max_ccb;
|
|
|
|
error = cdev_add(&ilo_hw->cdev, MKDEV(ilo_major, start), max_ccb);
|
2008-07-02 22:38:53 +07:00
|
|
|
if (error) {
|
|
|
|
dev_err(&pdev->dev, "Could not add cdev\n");
|
2009-08-18 06:07:33 +07:00
|
|
|
goto remove_isr;
|
2008-07-02 22:38:53 +07:00
|
|
|
}
|
|
|
|
|
2012-06-10 20:39:20 +07:00
|
|
|
for (minor = 0 ; minor < max_ccb; minor++) {
|
2008-07-02 22:38:53 +07:00
|
|
|
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;
|
2009-08-18 06:07:33 +07:00
|
|
|
remove_isr:
|
|
|
|
ilo_disable_interrupts(ilo_hw);
|
|
|
|
free_irq(pdev->irq, ilo_hw);
|
2008-07-02 22:38:53 +07:00
|
|
|
unmap:
|
|
|
|
ilo_unmap_device(pdev, ilo_hw);
|
|
|
|
free_regions:
|
|
|
|
pci_release_regions(pdev);
|
|
|
|
disable:
|
2012-09-13 21:06:48 +07:00
|
|
|
/* pci_disable_device(pdev); see comment in ilo_remove */
|
2008-07-02 22:38:53 +07:00
|
|
|
free:
|
|
|
|
kfree(ilo_hw);
|
|
|
|
out:
|
|
|
|
ilo_hwdev[devnum] = 0;
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2017-07-30 12:26:35 +07:00
|
|
|
static const struct pci_device_id ilo_devices[] = {
|
2008-07-02 22:38:53 +07:00
|
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_COMPAQ, 0xB204) },
|
2009-02-28 05:03:09 +07:00
|
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_HP, 0x3307) },
|
2008-07-02 22:38:53 +07:00
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, ilo_devices);
|
|
|
|
|
|
|
|
static struct pci_driver ilo_driver = {
|
|
|
|
.name = ILO_NAME,
|
|
|
|
.id_table = ilo_devices,
|
|
|
|
.probe = ilo_probe,
|
2012-11-20 01:21:23 +07:00
|
|
|
.remove = ilo_remove,
|
2008-07-02 22:38:53 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-09-20 02:50:01 +07:00
|
|
|
MODULE_VERSION("1.5.0");
|
2008-07-02 22:38:53 +07:00
|
|
|
MODULE_ALIAS(ILO_NAME);
|
|
|
|
MODULE_DESCRIPTION(ILO_NAME);
|
2015-09-18 10:32:13 +07:00
|
|
|
MODULE_AUTHOR("David Altobelli <david.altobelli@hpe.com>");
|
2008-07-02 22:38:53 +07:00
|
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
|
2012-06-10 20:39:20 +07:00
|
|
|
module_param(max_ccb, uint, 0444);
|
2015-09-18 10:32:12 +07:00
|
|
|
MODULE_PARM_DESC(max_ccb, "Maximum number of HP iLO channels to attach (8-24)(default=16)");
|
2012-06-10 20:39:20 +07:00
|
|
|
|
2008-07-02 22:38:53 +07:00
|
|
|
module_init(ilo_init);
|
|
|
|
module_exit(ilo_exit);
|