linux/drivers/s390/cio/airq.c
Peter Oberparleiter 4e8e56c671 [S390] cio: Extend adapter interrupt interface.
From: Cornelia Huck <cornelia.huck@de.ibm.com>

Change the adapter interrupt interface in order to allow multiple
adapter interrupt handlers to be registered. Indicators are now
allocated by cio instead of the device driver.

The qdio parts have been
Acked-by: Ursula Braun <ubraun@linux.vnet.ibm.com>

Signed-off-by: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
2008-01-26 14:11:00 +01:00

145 lines
3.2 KiB
C

/*
* drivers/s390/cio/airq.c
* Support for adapter interruptions
*
* Copyright IBM Corp. 1999,2007
* Author(s): Ingo Adlung <adlung@de.ibm.com>
* Cornelia Huck <cornelia.huck@de.ibm.com>
* Arnd Bergmann <arndb@de.ibm.com>
* Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/rcupdate.h>
#include <asm/airq.h>
#include "cio.h"
#include "cio_debug.h"
#define NR_AIRQS 32
#define NR_AIRQS_PER_WORD sizeof(unsigned long)
#define NR_AIRQ_WORDS (NR_AIRQS / NR_AIRQS_PER_WORD)
union indicator_t {
unsigned long word[NR_AIRQ_WORDS];
unsigned char byte[NR_AIRQS];
} __attribute__((packed));
struct airq_t {
adapter_int_handler_t handler;
void *drv_data;
};
static union indicator_t indicators;
static struct airq_t *airqs[NR_AIRQS];
static int register_airq(struct airq_t *airq)
{
int i;
for (i = 0; i < NR_AIRQS; i++)
if (!cmpxchg(&airqs[i], NULL, airq))
return i;
return -ENOMEM;
}
/**
* s390_register_adapter_interrupt() - register adapter interrupt handler
* @handler: adapter handler to be registered
* @drv_data: driver data passed with each call to the handler
*
* Returns:
* Pointer to the indicator to be used on success
* ERR_PTR() if registration failed
*/
void *s390_register_adapter_interrupt(adapter_int_handler_t handler,
void *drv_data)
{
struct airq_t *airq;
char dbf_txt[16];
int ret;
airq = kmalloc(sizeof(struct airq_t), GFP_KERNEL);
if (!airq) {
ret = -ENOMEM;
goto out;
}
airq->handler = handler;
airq->drv_data = drv_data;
ret = register_airq(airq);
if (ret < 0)
kfree(airq);
out:
snprintf(dbf_txt, sizeof(dbf_txt), "rairq:%d", ret);
CIO_TRACE_EVENT(4, dbf_txt);
if (ret < 0)
return ERR_PTR(ret);
else
return &indicators.byte[ret];
}
EXPORT_SYMBOL(s390_register_adapter_interrupt);
/**
* s390_unregister_adapter_interrupt - unregister adapter interrupt handler
* @ind: indicator for which the handler is to be unregistered
*/
void s390_unregister_adapter_interrupt(void *ind)
{
struct airq_t *airq;
char dbf_txt[16];
int i;
i = (int) ((addr_t) ind) - ((addr_t) &indicators.byte[0]);
snprintf(dbf_txt, sizeof(dbf_txt), "urairq:%d", i);
CIO_TRACE_EVENT(4, dbf_txt);
indicators.byte[i] = 0;
airq = xchg(&airqs[i], NULL);
/*
* Allow interrupts to complete. This will ensure that the airq handle
* is no longer referenced by any interrupt handler.
*/
synchronize_sched();
kfree(airq);
}
EXPORT_SYMBOL(s390_unregister_adapter_interrupt);
#define INDICATOR_MASK (0xffUL << ((NR_AIRQS_PER_WORD - 1) * 8))
void do_adapter_IO(void)
{
int w;
int i;
unsigned long word;
struct airq_t *airq;
/*
* Access indicator array in word-sized chunks to minimize storage
* fetch operations.
*/
for (w = 0; w < NR_AIRQ_WORDS; w++) {
word = indicators.word[w];
i = w * NR_AIRQS_PER_WORD;
/*
* Check bytes within word for active indicators.
*/
while (word) {
if (word & INDICATOR_MASK) {
airq = airqs[i];
if (likely(airq))
airq->handler(&indicators.byte[i],
airq->drv_data);
else
/*
* Reset ill-behaved indicator.
*/
indicators.byte[i] = 0;
}
word <<= 8;
i++;
}
}
}