blob: 5d8779c0272d0969e7e3cb9f45c710fc86c53cf3 [file] [log] [blame]
/* adapter_interrupts.c
*
* Adapter EIP-202 module responsible for interrupts.
*
*/
/*****************************************************************************
* Copyright (c) 2008-2022 by Rambus, Inc. and/or its subsidiaries.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
/*----------------------------------------------------------------------------
* This module implements (provides) the following interface(s):
*/
#include "adapter_interrupts.h"
/*----------------------------------------------------------------------------
* This module uses (requires) the following interface(s):
*/
// Default Adapter configuration
#include "c_adapter_eip202.h" // ADAPTER_*INTERRUPT*
// Driver Framework Basic Definitions API
#include "basic_defs.h" // bool, IDENTIFIER_NOT_USED
// Driver Framework C-RunTime Library API
#include "clib.h" // ZEROINIT
// EIP-201 Advanced interrupt Controller (AIC)
#include "eip201.h"
// Logging API
#include "log.h"
// Driver Framework Device API
#include "device_types.h" // Device_Handle_t
#include "device_mgmt.h" // Device_Find, Device_GetReference
// Linux Kernel API
#include <linux/interrupt.h> // request_irq, free_irq,
// DECLARE_TASKLET, tasklet_schedule,
// IRQ_DISABLED
#include <linux/irq.h> // IRQ_TYPE_LEVEL_HIGH
#include <linux/irqreturn.h> // irqreturn_t
#ifdef ADAPTER_EIP202_USE_UMDEVXS_IRQ
#include "umdevxs_interrupt.h"
#endif
/*----------------------------------------------------------------------------
* Definitions and macros
*/
#define MAX_OS_IRQS 256
#define ARRAY_ELEMCOUNT(_a) (sizeof(_a) / sizeof(_a[0]))
#define ADAPTERINT_REQUEST_IRQ_FLAGS (IRQF_SHARED)
// Data structure for each Advanced interrupt controller
typedef struct
{
Device_Handle_t Device;
char *name; // Device name (can be found with Device_Find().
int irq_idx; // if isIRQ, then index of device interrupt.
// If !isIRQ. then driver IRQ number to which AIC is connected.
int irq; // System IRQ number
int BitIRQs[32]; // Mapping from source bits to internal driver IRQ numbers.
bool isIRQ; // true if AIC has dedicated system IRQ line, false is it
// is cascaded from another AIC.
} Adapter_AIC_t;
// Data structure per interrupt
typedef struct
{
uint32_t SourceBitMask;
char * name;
Adapter_AIC_t *AIC;
char *AICName;
struct tasklet_struct tasklet;
Adapter_InterruptHandler_t Handler;
uint32_t Counter;
void *extra;
bool fHaveTasklet;
EIP201_Config_t Config;
} Adapter_Interrupt_t;
/*----------------------------------------------------------------------------
* Local variables
*/
static bool Adapter_IRQ_Initialized = false;
#define ADAPTER_EIP202_ADD_AIC(_name,_idx, isirq) \
{NULL, _name, _idx, -1, {0, }, isirq}
static Adapter_AIC_t Adapter_AICTable[] = { ADAPTER_EIP202_AICS };
#define ADAPTER_EIP202_ADD_IRQ(_name,_phy,_aicname,_tasklet,_pol) \
{(1<<(_phy)),#_name,NULL,_aicname,{},NULL,0,NULL,_tasklet, \
EIP201_CONFIG_##_pol}
static Adapter_Interrupt_t Adapter_IRQTable[] = { ADAPTER_EIP202_IRQS };
// Define maximum number of supported interrupts
#define ADAPTER_MAX_INTERRUPTS ARRAY_ELEMCOUNT(Adapter_IRQTable)
static Adapter_AIC_t * IRQ_AIC_Mapping[MAX_OS_IRQS];
/*----------------------------------------------------------------------------
* AdapterINT_GetActiveIntNr
*
* Returns 0..31 depending on the lowest '1' bit.
* Returns 32 when all bits are zero
*
* Using binary break-down algorithm.
*/
static inline int
AdapterINT_GetActiveIntNr(
uint32_t Sources)
{
unsigned int IntNr = 0;
unsigned int R16, R8, R4, R2;
if (Sources == 0)
return 32;
// if the lower 16 bits are empty, look at the upper 16 bits
R16 = Sources & 0xFFFF;
if (R16 == 0)
{
IntNr += 16;
R16 = Sources >> 16;
}
// if the lower 8 bits are empty, look at the high 8 bits
R8 = R16 & 0xFF;
if (R8 == 0)
{
IntNr += 8;
R8 = R16 >> 8;
}
R4 = R8 & 0xF;
if (R4 == 0)
{
IntNr += 4;
R4 = R8 >> 4;
}
R2 = R4 & 3;
if (R2 == 0)
{
IntNr += 2;
R2 = R4 >> 2;
}
// last two bits are trivial
// 00 => cannot happen
// 01 => +0
// 10 => +1
// 11 => +0
if (R2 == 2)
IntNr++;
return IntNr;
}
/*----------------------------------------------------------------------------
* AdapterINT_Report_InterruptCounters
*/
static void
AdapterINT_Report_InterruptCounters(void)
{
int i;
for (i=0; i<ARRAY_ELEMCOUNT(Adapter_IRQTable); i++)
{
if ( (1<<i) & ADAPTER_EIP202_INTERRUPTS_TRACEFILTER)
{
LOG_CRIT("AIC %s interrupt source %s mask %08x counter %d\n",
Adapter_IRQTable[i].AICName,
Adapter_IRQTable[i].name,
Adapter_IRQTable[i].SourceBitMask,
Adapter_IRQTable[i].Counter);
}
}
}
/*----------------------------------------------------------------------------
* Adapter_Interrupt_Enable
*/
int
Adapter_Interrupt_Enable(
const int nIRQ,
const unsigned int Flags)
{
int rc = -1;
IDENTIFIER_NOT_USED(Flags);
LOG_INFO("\n\t\t %s \n", __func__);
if (nIRQ < 0 || nIRQ >= ADAPTER_MAX_INTERRUPTS)
{
LOG_CRIT(
"Adapter_Interrupt_Enable: "
"Failed, IRQ %d not supported\n",
nIRQ);
}
else
{
rc=EIP201_SourceMask_EnableSource(Adapter_IRQTable[nIRQ].AIC->Device,
Adapter_IRQTable[nIRQ].SourceBitMask);
LOG_INFO("\n\t\t\tAdapter_Interrupt_Enable "
"IRQ %d %s %s mask=%08x\n",
nIRQ,
Adapter_IRQTable[nIRQ].AICName,
Adapter_IRQTable[nIRQ].name,
Adapter_IRQTable[nIRQ].SourceBitMask);
}
LOG_INFO("\n\t\t %s done \n", __func__);
return rc;
}
/*----------------------------------------------------------------------------
* Adapter_Interrupt_Disable
*/
int
Adapter_Interrupt_Disable(
const int nIRQ,
const unsigned int Flags)
{
int rc = -1;
IDENTIFIER_NOT_USED(Flags);
LOG_INFO("\n\t\t %s \n", __func__);
if (nIRQ < 0 || nIRQ >= ADAPTER_MAX_INTERRUPTS)
{
LOG_CRIT(
"Adapter_Interrupt_Disable: "
"Failed, IRQ %d not supported\n",
nIRQ);
}
else
{
rc = EIP201_SourceMask_DisableSource(Adapter_IRQTable[nIRQ].AIC->Device,
Adapter_IRQTable[nIRQ].SourceBitMask);
LOG_INFO("\n\t\t\tAdapter_Interrupt_Disable "
"IRQ %d %s %s mask=%08x\n",
nIRQ,
Adapter_IRQTable[nIRQ].AICName,
Adapter_IRQTable[nIRQ].name,
Adapter_IRQTable[nIRQ].SourceBitMask);
}
LOG_INFO("\n\t\t %s done \n", __func__);
return rc;
}
/*----------------------------------------------------------------------------
* Adapter_Interrupt_SetHandler
*/
int
Adapter_Interrupt_SetHandler(
const int nIRQ,
Adapter_InterruptHandler_t HandlerFunction)
{
if (nIRQ < 0 || nIRQ >= ADAPTER_MAX_INTERRUPTS)
return -1;
LOG_INFO(
"Adapter_Interrupt_SetHandler: "
"HandlerFnc=%p for IRQ %d\n",
HandlerFunction,
nIRQ);
Adapter_IRQTable[nIRQ].Handler = HandlerFunction;
return 0;
}
/*----------------------------------------------------------------------------
* AdapterINT_CommonTasklet
*
* This handler is scheduled in the top-halve interrupt handler when it
* decodes one of the CDR or RDR interrupt sources.
* The data parameter is the IRQ value (from adapter_interrupts.h) for that
* specific interrupt source.
*/
static void
AdapterINT_CommonTasklet(
unsigned long data)
{
const unsigned int IntNr = (unsigned int)data;
Adapter_InterruptHandler_t H;
LOG_INFO("\n\t\t%s \n", __func__);
LOG_INFO("Tasklet invoked intnr=%d\n",IntNr);
// verify we have a handler
H = Adapter_IRQTable[IntNr].Handler;
if (H)
{
// invoke the handler
H(IntNr, 0);
}
else
{
LOG_CRIT(
"AdapterINT_CommonTasklet: "
"Error, disabling IRQ %d with missing handler\n",
IntNr);
Adapter_Interrupt_Disable(IntNr, 0);
}
LOG_INFO("\n\t\t%s done\n", __func__);
}
/*----------------------------------------------------------------------------
* AdapterINT_AICHandler
*
* Handle all interrupts connected to the specified AIC.
*
* If this AIC is connected directly to a system IRQ line, this is
* called directly from the Top Half Handler.
*
* If this AIC is connected via an IRQ line of another AIC, this is
* called from the handler function of that interrupt.
*
* Return: 0 for success, -1 for failure.
*/
static int
AdapterINT_AICHandler(Adapter_AIC_t *AIC)
{
EIP201_SourceBitmap_t Sources;
int IntNr, irq, rc = 0;
LOG_INFO("\n\t\t%s \n", __func__);
if (AIC == NULL)
return -1;
if (AIC->Device == NULL)
{
LOG_INFO("%s: skipping spurious interrupt for AIC %s, IRQ %d\n",
__func__,
AIC->name,
AIC->irq);
goto exit; // no error
}
Sources = EIP201_SourceStatus_ReadAllEnabled(AIC->Device);
if (Sources == 0)
{
rc = -1;
goto exit; // error
}
EIP201_Acknowledge(AIC->Device, Sources);
LOG_INFO("%s: AIC %s, IRQ %d, sources=%x\n",
__func__,
AIC->name,
AIC->irq,
Sources);
while (Sources)
{
IntNr = AdapterINT_GetActiveIntNr(Sources);
/* Get number of first bit set */
Sources &= ~(1<<IntNr);
/* Clear this in sources */
irq = AIC->BitIRQs[IntNr];
LOG_INFO("%s: Handle IRQ %d for AIC %s\n", __func__, irq, AIC->name);
if (irq < 0 || irq >= ADAPTER_MAX_INTERRUPTS)
{
LOG_CRIT("%s: %s IRQ not defined for bit %d, disabling source\n",
__func__,
AIC->name,
IntNr);
EIP201_SourceMask_DisableSource(AIC->Device, (1<<IntNr));
}
Adapter_IRQTable[irq].Counter++;
if ( (1<<irq) & ADAPTER_EIP202_INTERRUPTS_TRACEFILTER)
LOG_CRIT("%s: encountered interrupt %d, bit %d for AIC %s\n",
__func__,
irq,
IntNr,
AIC->name);
if(Adapter_IRQTable[irq].fHaveTasklet)
{
LOG_INFO("%s: Start tasklet\n", __func__);
/* IRQ is handled via tasklet */
tasklet_schedule(&Adapter_IRQTable[irq].tasklet);
Adapter_Interrupt_Disable(irq, 0);
}
else
{
Adapter_InterruptHandler_t H = Adapter_IRQTable[irq].Handler;
LOG_INFO("%s: Run normal handler\n", __func__);
/* Handler is called directly */
if (H)
{
H(irq, 0);
}
else
{
LOG_CRIT(
"%s : Error, disabling IRQ %d with missing handler\n",
__func__,
irq);
Adapter_Interrupt_Disable(irq, 0);
}
}
} // while
exit:
LOG_INFO("\n\t\t%s done\n", __func__);
return rc;
}
/*----------------------------------------------------------------------------
* AdapterINT_TopHalfHandler
*
* This is the interrupt handler function call by the kernel when our hooked
* interrupt is active.
*
* Call the handler for the associated AIC.
*/
static irqreturn_t
AdapterINT_TopHalfHandler(
int irq,
void * dev_id)
{
irqreturn_t Int_Rc = IRQ_NONE;
LOG_INFO("\n\t\t%s \n", __func__);
if (irq < 0 || irq >= MAX_OS_IRQS || IRQ_AIC_Mapping[irq]==NULL)
{
LOG_CRIT("%s: No AIC defined for IRQ %d\n",__func__,irq);
goto error;
}
if ( AdapterINT_AICHandler(IRQ_AIC_Mapping[irq]) < 0)
{
goto error;
}
Int_Rc = IRQ_HANDLED;
error:
LOG_INFO("\n\t\t%s done\n", __func__);
IDENTIFIER_NOT_USED(dev_id);
return Int_Rc;
}
/*----------------------------------------------------------------------------
* AdapterINT_ChainedAIC
*
* Handler function for IRQ that services an entire AIC.
*/
static void
AdapterINT_ChainedAIC(const int irq, const unsigned int flags)
{
IDENTIFIER_NOT_USED(flags);
AdapterINT_AICHandler(Adapter_IRQTable[irq].extra);
}
/*----------------------------------------------------------------------------
* AdapterINT_SetInternalLinkage
*
* Create AIC References in Adapter_IRQTable.
* Fill in BitIRQs references in Adapter_AICTable.
* Perform some consistency checks.
*
* Return 0 on success, -1 on failure.
*/
static int
AdapterINT_SetInternalLinkage(void)
{
int i,j;
int IntNr;
for (i=0; i<ARRAY_ELEMCOUNT(Adapter_IRQTable); i++)
{
Adapter_IRQTable[i].AIC = NULL;
}
for (i=0; i<ARRAY_ELEMCOUNT(Adapter_AICTable); i++)
{
for (j=0; j<32; j++)
{
Adapter_AICTable[i].BitIRQs[j] = -1;
}
for (j=0; j<ARRAY_ELEMCOUNT(Adapter_IRQTable); j++)
{
if (strcmp(Adapter_AICTable[i].name, Adapter_IRQTable[j].AICName)
== 0)
{
if (Adapter_IRQTable[j].AIC)
{
LOG_CRIT("%s: AIC link set more than once\n",__func__);
}
Adapter_IRQTable[j].AIC = Adapter_AICTable + i;
IntNr = AdapterINT_GetActiveIntNr(
Adapter_IRQTable[j].SourceBitMask);
if (IntNr < 0 || IntNr >= 32)
{
LOG_CRIT("%s: IRQ %d source bit %d out of range\n",
__func__,j,IntNr);
return -1;
}
else if (Adapter_AICTable[i].BitIRQs[IntNr] >= 0)
{
LOG_CRIT(
"%s: AIC %s IRQ %d source bit %d already defined\n",
__func__,
Adapter_AICTable[i].name,
j,
IntNr);
return -1;
}
else
{
Adapter_AICTable[i].BitIRQs[IntNr] = j;
}
}
}
}
for (i=0; i<ARRAY_ELEMCOUNT(Adapter_IRQTable); i++)
{
if (Adapter_IRQTable[i].AIC == NULL)
{
LOG_CRIT("%s: AIC pointer of IRQ %d is null\n",__func__,i);
return -1;
}
}
return 0;
}
/*----------------------------------------------------------------------------
* AdapterINT_AIC_Init
*
*/
static bool
AdapterINT_AIC_Init(void)
{
EIP201_Status_t res;
unsigned int i;
// Initialize all configured EIP-201 AIC devices
for (i = 0; i < ARRAY_ELEMCOUNT(Adapter_AICTable); i++)
{
LOG_INFO("%s: Initialize AIC %s\n",__func__,Adapter_AICTable[i].name);
Adapter_AICTable[i].Device = Device_Find(Adapter_AICTable[i].name);
if (Adapter_AICTable[i].Device == NULL)
{
LOG_CRIT("%s: Device_Find() failed for %s\n",
__func__,
Adapter_AICTable[i].name);
return false; // error
}
res = EIP201_Initialize(Adapter_AICTable[i].Device, NULL, 0);
if (res != EIP201_STATUS_SUCCESS)
{
LOG_CRIT("%s: EIP201_Initialize() failed, error %d\n", __func__, res);
return false; // error
}
}
return true; // success
}
/*----------------------------------------------------------------------------
* AdapterINT_AIC_Enable
*
*/
static void
AdapterINT_AIC_Enable(void)
{
unsigned int i;
for (i = 0; i < ARRAY_ELEMCOUNT(Adapter_AICTable); i++)
if (!Adapter_AICTable[i].isIRQ)
Adapter_Interrupt_Enable(Adapter_AICTable[i].irq_idx, 0);
}
/*----------------------------------------------------------------------------
* Adapter_Interrupts_Init
*
*/
int
Adapter_Interrupts_Init(
const int nIRQ)
{
int i;
int IntNr = nIRQ;
LOG_INFO("\n\t\t %s \n", __func__);
if (AdapterINT_SetInternalLinkage() < 0)
{
LOG_CRIT("Interrupt AIC and IRQ tables are inconsistent\n");
return -1;
}
// Initialize the AIC devices
if (!AdapterINT_AIC_Init())
return -1;
// Initialize the Adapter_IRQTable and tasklets.
for (i=0; i<ARRAY_ELEMCOUNT(Adapter_IRQTable); i++)
{
Adapter_IRQTable[i].Handler = NULL;
Adapter_IRQTable[i].extra = NULL;
Adapter_IRQTable[i].Counter = 0;
if (Adapter_IRQTable[i].fHaveTasklet)
tasklet_init(&Adapter_IRQTable[i].tasklet,
AdapterINT_CommonTasklet,
(long)i);
EIP201_Config_Change(Adapter_IRQTable[i].AIC->Device,
Adapter_IRQTable[i].SourceBitMask,
Adapter_IRQTable[i].Config);
// Clear any pending egde-sensitive interrupts.
EIP201_Acknowledge(Adapter_IRQTable[i].AIC->Device,
Adapter_IRQTable[i].SourceBitMask);
}
// Request the IRQs for each AIC or register to IRQ of other AIC.
for (i=0; i<ARRAY_ELEMCOUNT(Adapter_AICTable); i++)
{
if (Adapter_AICTable[i].isIRQ)
{
int res;
LOG_INFO("\n\t\t %s: Request IRQ for AIC %s\n",
__func__,Adapter_AICTable[i].name);
#ifdef ADAPTER_EIP202_USE_UMDEVXS_IRQ
res = UMDevXS_Interrupt_Request(AdapterINT_TopHalfHandler,
Adapter_AICTable[i].irq_idx);
IntNr = res;
#else
{
struct device * Device_p;
// Get device reference for this resource
Device_p = Device_GetReference(NULL, NULL);
res = request_irq(IntNr,
AdapterINT_TopHalfHandler,
ADAPTERINT_REQUEST_IRQ_FLAGS,
ADAPTER_EIP202_DRIVER_NAME,
Device_p);
}
#endif
if (res < 0)
{
LOG_CRIT("%s: Request IRQ error %d\n", __func__, res);
return res;
}
else
{
Adapter_AICTable[i].irq = IntNr;
IRQ_AIC_Mapping[IntNr] = Adapter_AICTable + i;
LOG_INFO("%s: Successfully hooked IRQ %d\n", __func__, IntNr);
}
}
else
{
IntNr = Adapter_AICTable[i].irq_idx;
LOG_INFO("%s: Hook up AIC %s to chained IRQ %d\n",
__func__,
Adapter_AICTable[i].name,
IntNr);
if (IntNr < 0 || IntNr >= ADAPTER_MAX_INTERRUPTS)
{
LOG_CRIT("%s: IRQ %d out of range\n", __func__,IntNr);
}
Adapter_IRQTable[IntNr].extra = Adapter_AICTable + i;
Adapter_Interrupt_SetHandler(IntNr, AdapterINT_ChainedAIC);
}
}
// Enable AIC
AdapterINT_AIC_Enable();
LOG_INFO("\n\t\t %s done\n", __func__);
Adapter_IRQ_Initialized = true;
return 0;
}
/*----------------------------------------------------------------------------
* Adapter_Interrupts_UnInit
*/
int
Adapter_Interrupts_UnInit(const int nIRQ)
{
unsigned int i;
IDENTIFIER_NOT_USED(nIRQ);
LOG_INFO("\n\t\t %s \n", __func__);
if (!Adapter_IRQ_Initialized)
return -1;
// disable all interrupts
for (i = 0; i < ARRAY_ELEMCOUNT(Adapter_AICTable); i++)
{
if (Adapter_AICTable[i].Device)
{
EIP201_SourceMask_DisableSource(Adapter_AICTable[i].Device,
EIP201_SOURCE_ALL);
Adapter_AICTable[i].Device = NULL;
}
if(Adapter_AICTable[i].isIRQ && Adapter_AICTable[i].irq > 0)
{
#ifdef ADAPTER_EIP202_USE_UMDEVXS_IRQ
UMDevXS_Interrupt_Request(NULL,Adapter_AICTable[i].irq_idx);
#else
// Get device reference for this resource
struct device * Device_p = Device_GetReference(NULL, NULL);
LOG_INFO("%s: Free IRQ %d for AIC %s\n",
__func__,
Adapter_AICTable[i].irq,
Adapter_AICTable[i].name);
// unhook the interrupt
free_irq(Adapter_AICTable[i].irq, Device_p);
LOG_INFO("%s: Successfully freed IRQ %d for AIC %s\n",
__func__,
Adapter_AICTable[i].irq,
Adapter_AICTable[i].name);
#endif
}
}
// Kill all tasklets
for (i = 0; i < ARRAY_ELEMCOUNT(Adapter_IRQTable); i++)
if (Adapter_IRQTable[i].fHaveTasklet)
tasklet_kill(&Adapter_IRQTable[i].tasklet);
AdapterINT_Report_InterruptCounters();
ZEROINIT(IRQ_AIC_Mapping);
Adapter_IRQ_Initialized = false;
LOG_INFO("\n\t\t %s done\n", __func__);
return 0;
}
#ifdef ADAPTER_PEC_RPM_EIP202_DEVICE0_ID
/*----------------------------------------------------------------------------
* Adapter_Interrupts_Resume
*/
int
Adapter_Interrupts_Resume(void)
{
LOG_INFO("\n\t\t %s \n", __func__);
if (!Adapter_IRQ_Initialized)
{
LOG_CRIT("%s: failed, not initialized\n", __func__);
return -1;
}
// Resume AIC devices
if (!AdapterINT_AIC_Init())
return -2; // error
// Re-enable AIC interrupts
AdapterINT_AIC_Enable();
LOG_INFO("\n\t\t %s done\n", __func__);
return 0; // success
}
#endif
/* end of file adapter_interrupts.c */