px_intr.c revision c17ca212a164f33e059475e39740f789fe154b5b
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
/*
* PX nexus interrupt handling:
* PX device interrupt handler wrapper
* PIL lookup routine
* PX device interrupt related initchild code
*/
#include <sys/ddi_impldefs.h>
#include "px_obj.h"
/*
* interrupt jabber:
*
* When an interrupt line is jabbering, every time the state machine for the
* associated ino is idled, a new mondo will be sent and the ino will go into
* the pending state again. The mondo will cause a new call to
* px_intr_wrapper() which normally idles the ino's state machine which would
* precipitate another trip round the loop.
*
* The loop can be broken by preventing the ino's state machine from being
* idled when an interrupt line is jabbering. See the comment at the
* beginning of px_intr_wrapper() explaining how the 'interrupt jabber
* protection' code does this.
*/
/*LINTLIBRARY*/
/*
* If the unclaimed interrupt count has reached the limit set by
* pci_unclaimed_intr_max within the time limit, then all interrupts
* on this ino is blocked by not idling the interrupt state machine.
*/
static int
{
char *err_fmt_str;
int i;
return (DDI_INTR_CLAIMED);
if (!ino_p->ino_unclaimed_intrs)
goto clear;
> px_spurintr_duration) {
ino_p->ino_unclaimed_intrs = 0;
goto clear;
}
err_fmt_str = "%s%d: ino 0x%x blocked";
goto warn;
err_fmt_str = "!%s%d: spurious interrupt from ino 0x%x";
warn:
}
/* Clear the pending state */
return (DDI_INTR_UNCLAIMED);
}
return (DDI_INTR_CLAIMED);
}
extern uint64_t intr_get_time(void);
/*
* px_intx_intr (INTx or legacy interrupt handler)
*
* This routine is used as wrapper around interrupt handlers installed by child
* device drivers. This routine invokes the driver interrupt handlers and
* examines the return codes.
*
* There is a count of unclaimed interrupts kept on a per-ino basis. If at
* least one handler claims the interrupt then the counter is halved and the
* interrupt state machine is idled. If no handler claims the interrupt then
* the counter is incremented by one and the state machine is idled.
* If the count ever reaches the limit value set by pci_unclaimed_intr_max
* then the interrupt state machine is not idled thus preventing any further
* interrupts on that ino. The state machine will only be idled again if a
* handler is subsequently added or removed.
*
* return value: DDI_INTR_CLAIMED if any handlers claimed the interrupt,
* DDI_INTR_UNCLAIMED otherwise.
*/
{
int i;
"ino=%x sysino=%llx pil=%x ih_size=%x ih_lst=%x\n",
"px_intx_intr: %s%d interrupt %d is disabled\n",
continue;
}
"ino=%x handler=%p arg1 =%p arg2 = %p\n",
/*
* Account for time used by this interrupt. Protect against
* conflicting writes to ih_ticks from ib_intr_dist_all() by
* using atomic ops.
*/
if (pil <= LOCK_LEVEL)
result += r;
continue;
if (result)
break;
}
if (result)
/* Interrupt can only be cleared after all pil levels are handled */
return (DDI_INTR_CLAIMED);
if (!ino_p->ino_claimed) {
return (px_spurintr(ipil_p));
}
ino_p->ino_unclaimed_intrs = 0;
ino_p->ino_claimed = 0;
/* Clear the pending state */
return (DDI_INTR_UNCLAIMED);
return (DDI_INTR_CLAIMED);
}
/*
* px_msiq_intr (MSI/X or PCIe MSG interrupt handler)
*
* This routine is used as wrapper around interrupt handlers installed by child
* device drivers. This routine invokes the driver interrupt handlers and
* examines the return codes.
*
* There is a count of unclaimed interrupts kept on a per-ino basis. If at
* least one handler claims the interrupt then the counter is halved and the
* interrupt state machine is idled. If no handler claims the interrupt then
* the counter is incremented by one and the state machine is idled.
* If the count ever reaches the limit value set by pci_unclaimed_intr_max
* then the interrupt state machine is not idled thus preventing any further
* interrupts on that ino. The state machine will only be idled again if a
* handler is subsequently added or removed.
*
* return value: DDI_INTR_CLAIMED if any handlers claimed the interrupt,
* DDI_INTR_UNCLAIMED otherwise.
*/
{
int i, j;
/*
* The px_msiq_intr() handles multiple interrupt priorities and it
* will set msiq->msiq_rec2process to the number of MSIQ records to
* process while handling the highest priority interrupt. Subsequent
* lower priority interrupts will just process any unprocessed MSIQ
* records or will just return immediately.
*/
if (msiq_p->msiq_recs2process == 0) {
/* Read current MSIQ tail index */
}
/* If all MSIQ records are already processed, just return immediately */
== msiq_p->msiq_recs2process)
goto intr_done;
/*
* Calculate the number of recs to process by taking the difference
* between the head and tail pointers. For all records we always
* verify that we have a valid record type before we do any processing.
* If triggered, we should always have at least one valid record.
*/
for (i = 0; i < msiq_p->msiq_recs2process; i++) {
/* Read next MSIQ record */
"msiq_rec_type 0x%llx msiq_rec_rid 0x%llx\n",
if (!rec_type)
goto next_rec;
/* Check MSIQ record type */
switch (rec_type) {
case MSG_REC:
"record, msg type 0x%x\n", msg_code);
break;
case MSI32_REC:
case MSI64_REC:
"msi 0x%x\n", msg_code);
break;
default:
msg_code = 0;
"record type is not supported",
rec_type);
goto next_rec;
}
/*
* Scan through px_ih_t linked list, searching for the
* right px_ih_t, matching MSIQ record data.
*/
;
"data=%x handler=%p arg1 =%p arg2=%p\n",
/*
* Special case for PCIE Error Messages.
* The current frame work doesn't fit PCIE Err Msgs
* This should be fixed when PCIE MESSAGES as a whole
* is architected correctly.
*/
((msg_code == PCIE_MSG_CODE_ERR_COR) ||
(msg_code == PCIE_MSG_CODE_ERR_NONFATAL) ||
(msg_code == PCIE_MSG_CODE_ERR_FATAL))) {
} else {
/* Clear MSI state */
}
/*
* Account for time used by this interrupt. Protect
* against conflicting writes to ih_ticks from
* ib_intr_dist_all() by using atomic ops.
*/
if (pil <= LOCK_LEVEL)
/* clear handler status flags */
} else {
"No matching MSIQ record found\n");
}
/* Get the pointer next EQ record */
curr_head_p = (msiqhead_t *)
/* Check for overflow condition */
}
/* ino_claimed used just for debugging purpose */
if (ret)
/* Interrupt can only be cleared after all pil levels are handled */
if (--ino_p->ino_ipil_cntr != 0)
return (DDI_INTR_CLAIMED);
return (px_spurintr(ipil_p));
}
/* Update MSIQ head index with no of MSIQ records processed */
msiq_p->msiq_new_head_index = 0;
msiq_p->msiq_recs2process = 0;
ino_p->ino_claimed = 0;
/* Clear the pending state */
return (DDI_INTR_UNCLAIMED);
return (DDI_INTR_CLAIMED);
}
{
;
return (cdip);
}
/* ARGSUSED */
int
{
int ret = DDI_SUCCESS;
switch (intr_op) {
case DDI_INTROP_GETCAP:
break;
case DDI_INTROP_SETCAP:
ret = DDI_ENOTSUP;
break;
case DDI_INTROP_ALLOC:
break;
case DDI_INTROP_FREE:
break;
case DDI_INTROP_GETPRI:
break;
case DDI_INTROP_SETPRI:
break;
case DDI_INTROP_ADDISR:
break;
case DDI_INTROP_REMISR:
break;
case DDI_INTROP_GETTARGET:
break;
case DDI_INTROP_SETTARGET:
ret = DDI_ENOTSUP;
break;
case DDI_INTROP_ENABLE:
break;
case DDI_INTROP_DISABLE:
break;
case DDI_INTROP_SETMASK:
break;
case DDI_INTROP_CLRMASK:
break;
case DDI_INTROP_GETPENDING:
break;
case DDI_INTROP_NINTRS:
case DDI_INTROP_NAVAIL:
break;
default:
ret = DDI_ENOTSUP;
break;
}
return (ret);
}
/* ARGSUSED */
int
{
int ret = DDI_SUCCESS;
/* Check for MSI64 support */
} else {
}
switch (intr_op) {
case DDI_INTROP_GETCAP:
break;
case DDI_INTROP_SETCAP:
ret = DDI_ENOTSUP;
break;
case DDI_INTROP_ALLOC:
/*
* We need to restrict this allocation in future
* based on Resource Management policies.
*/
(int *)result)) != DDI_SUCCESS) {
"failed, rdip 0x%p type 0x%d inum 0x%x "
hdlp->ih_scratch1);
return (ret);
}
break;
}
"failed, rdip 0x%p inum 0x%x\n", rdip,
hdlp->ih_scratch1);
return (DDI_FAILURE);
}
break;
case DDI_INTROP_FREE:
goto msi_free;
break;
(i_ddi_get_msix(rdip))) {
}
hdlp->ih_scratch1);
break;
case DDI_INTROP_GETPRI:
break;
case DDI_INTROP_SETPRI:
break;
case DDI_INTROP_ADDISR:
return (ret);
}
return (ret);
}
PCI_MSI_STATE_IDLE)) != DDI_SUCCESS) {
return (ret);
}
PCI_MSI_VALID)) != DDI_SUCCESS)
return (ret);
break;
case DDI_INTROP_DUPVEC:
hdlp->ih_scratch1);
break;
case DDI_INTROP_REMISR:
&msiq_id)) != DDI_SUCCESS)
return (ret);
msi_num)) != DDI_SUCCESS)
return (ret);
PCI_MSI_INVALID)) != DDI_SUCCESS)
return (ret);
return (ret);
break;
case DDI_INTROP_GETTARGET:
&msiq_id)) != DDI_SUCCESS)
return (ret);
break;
case DDI_INTROP_SETTARGET:
break;
case DDI_INTROP_ENABLE:
/*
* For MSI, just clear the mask bit and return if curr_nenables
* is > 1. For MSI-X, program MSI address and data for every
* MSI-X vector including dup vectors irrespective of current
* curr_nenables value.
*/
return (ret);
return (ret);
}
}
return (ret);
break;
case DDI_INTROP_DISABLE:
return (ret);
/*
* curr_nenables will be greater than 1 if rdip is using
* MSI-X and also, if it is using DUP interface. If this
* curr_enables is > 1, return after setting the mask bit.
*/
return (DDI_SUCCESS);
!= DDI_SUCCESS)
return (ret);
break;
case DDI_INTROP_BLOCKENABLE:
return (ret);
break;
case DDI_INTROP_BLOCKDISABLE:
break;
case DDI_INTROP_SETMASK:
break;
case DDI_INTROP_CLRMASK:
break;
case DDI_INTROP_GETPENDING:
break;
case DDI_INTROP_NINTRS:
break;
case DDI_INTROP_NAVAIL:
/* XXX - a new interface may be needed */
break;
case DDI_INTROP_GETPOOL:
return (DDI_ENOTSUP);
}
ret = DDI_SUCCESS;
break;
default:
ret = DDI_ENOTSUP;
break;
}
return (ret);
}
static struct {
} pxintr_ks_template = {
{ "name", KSTAT_DATA_CHAR },
{ "type", KSTAT_DATA_CHAR },
{ "cpu", KSTAT_DATA_UINT64 },
{ "pil", KSTAT_DATA_UINT64 },
{ "time", KSTAT_DATA_UINT64 },
{ "ino", KSTAT_DATA_UINT64 },
{ "cookie", KSTAT_DATA_UINT64 },
{ "devpath", KSTAT_DATA_STRING },
{ "buspath", KSTAT_DATA_STRING },
};
static uint32_t pxintr_ks_instance;
static char ih_devpath[MAXPATHLEN];
static char ih_buspath[MAXPATHLEN];
int
{
DDI_SUCCESS) {
"failed");
}
case DDI_INTR_TYPE_MSI:
"msi");
break;
case DDI_INTR_TYPE_MSIX:
"msix");
break;
default:
"fixed");
break;
}
} else {
"disabled");
}
return (0);
}
void
{
/*
* Create pci_intrs::: kstats for all ih types except messages,
* which represent unusual conditions and don't need to be tracked.
*/
"interrupts", KSTAT_TYPE_NAMED,
sizeof (pxintr_ks_template) / sizeof (kstat_named_t),
}
}
}
/*
* px_add_intx_intr:
*
* This function is called to register INTx and legacy hardware
* interrupt pins interrupts.
*/
int
{
int ret = DDI_SUCCESS;
/* Sharing the INO using a PIL that already exists */
ret = DDI_FAILURE;
goto fail1;
}
/* Save mondo value in hdlp */
ih_p)) != DDI_SUCCESS)
goto fail1;
goto ino_done;
}
/* Sharing the INO using a new PIL */
/*
* disable INO to avoid lopil race condition with
* px_intx_intr
*/
&curr_cpu)) != DDI_SUCCESS) {
"px_add_intx_intr px_intr_gettarget() failed\n");
goto fail1;
}
/* Wait on pending interrupt */
DDI_SUCCESS) {
"pending sysino 0x%lx(ino 0x%x) timeout",
goto fail1;
}
}
/* Save mondo value in hdlp */
/*
* Restore original interrupt handler
* and arguments in interrupt handle.
*/
if (ret != DDI_SUCCESS)
goto fail2;
/* Save the pil for this ino */
/* Select cpu, saving it for sharing and removal */
/* Enable interrupt */
} else {
/* Re-enable interrupt */
}
/* Add weight to the cpu that we are already targeting */
return (ret);
if (ih_p->ih_config_handle)
return (ret);
}
/*
* px_rem_intx_intr:
*
* This function is called to unregister INTx and legacy hardware
* interrupt pins interrupts.
*/
int
{
int ret = DDI_SUCCESS;
/* Get the current cpu */
&curr_cpu)) != DDI_SUCCESS)
goto fail;
goto fail;
if (ipil_p->ipil_ih_size == 0) {
}
if (ino_p->ino_ipil_size == 0) {
} else {
/* Re-enable interrupt only if mapping register still shared */
}
fail:
return (ret);
}
/*
* px_add_msiq_intr:
*
*/
int
{
int ret = DDI_SUCCESS;
if (ret != DDI_SUCCESS) {
"msiq allocation failed\n");
goto fail;
}
/* Sharing ino */
ret = DDI_FAILURE;
goto fail1;
}
/* Save mondo value in hdlp */
ih_p)) != DDI_SUCCESS)
goto fail1;
goto ino_done;
}
/* Save mondo value in hdlp */
/*
* Restore original interrupt handler
* and arguments in interrupt handle.
*/
if (ret != DDI_SUCCESS)
goto fail2;
/* Save the pil for this ino */
/* Select cpu, saving it for sharing and removal */
/* Enable MSIQ */
/* Enable interrupt */
}
/* Add weight to the cpu that we are already targeting */
return (ret);
fail:
if (ih_p->ih_config_handle)
return (ret);
}
/*
* px_rem_msiq_intr:
*
*/
int
{
int ret = DDI_SUCCESS;
msg_code);
/* Get the current cpu */
&curr_cpu)) != DDI_SUCCESS)
goto fail;
goto fail;
if (ipil_p->ipil_ih_size == 0) {
if (ino_p->ino_ipil_size == 0)
}
if (ino_p->ino_ipil_size) {
/* Re-enable interrupt only if mapping register still shared */
}
fail:
return (ret);
}