pci_intr.c revision 09b1eac246a4e627fcbd1ce5bf8005746cbe45ea
/*
* 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
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* PCI nexus interrupt handling:
* PCI device interrupt handler wrapper
* pil lookup routine
* PCI device interrupt related initchild code
*/
#include <sys/ddi_impldefs.h>
#ifdef _STARFIRE
#include <sys/starfire.h>
#endif /* _STARFIRE */
/*
* 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
* pci_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 pci_intr_wrapper() explaining how the 'interrupt jabber
* protection' code does this.
*/
/*LINTLIBRARY*/
#ifdef NOT_DEFINED
/*
* This array is used to determine the sparc PIL at the which the
* handler for a given INO will execute. This table is for onboard
* devices only. A different scheme will be used for plug-in cards.
*/
uint_t ino_to_pil[] = {
/* pil */ /* ino */
0, 0, 0, 0, /* 0x00 - 0x03: bus A slot 0 int#A, B, C, D */
0, 0, 0, 0, /* 0x04 - 0x07: bus A slot 1 int#A, B, C, D */
0, 0, 0, 0, /* 0x08 - 0x0B: unused */
0, 0, 0, 0, /* 0x0C - 0x0F: unused */
0, 0, 0, 0, /* 0x10 - 0x13: bus B slot 0 int#A, B, C, D */
0, 0, 0, 0, /* 0x14 - 0x17: bus B slot 1 int#A, B, C, D */
0, 0, 0, 0, /* 0x18 - 0x1B: bus B slot 2 int#A, B, C, D */
4, 0, 0, 0, /* 0x1C - 0x1F: bus B slot 3 int#A, B, C, D */
4, /* 0x20: SCSI */
6, /* 0x21: ethernet */
3, /* 0x22: parallel port */
9, /* 0x23: audio record */
9, /* 0x24: audio playback */
14, /* 0x25: power fail */
4, /* 0x26: 2nd SCSI */
8, /* 0x27: floppy */
14, /* 0x28: thermal warning */
12, /* 0x29: keyboard */
12, /* 0x2A: mouse */
12, /* 0x2B: serial */
14, /* 0x2E: uncorrectable ECC errors */
14, /* 0x2F: correctable ECC errors */
14, /* 0x30: PCI bus A error */
14, /* 0x31: PCI bus B error */
14, /* 0x32: power management wakeup */
14, /* 0x33 */
14, /* 0x34 */
14, /* 0x35 */
14, /* 0x36 */
14, /* 0x37 */
14, /* 0x38 */
14, /* 0x39 */
14, /* 0x3a */
14, /* 0x3b */
14, /* 0x3c */
14, /* 0x3d */
14, /* 0x3e */
14, /* 0x3f */
14 /* 0x40 */
};
#endif /* NOT_DEFINED */
/*
* map_pcidev_cfg_reg - create mapping to pci device configuration registers
* if we have a simba AND a pci to pci bridge along the
* device path.
* Called with corresponding mutexes held!!
*
* XXX XXX XXX The purpose of this routine is to overcome a hardware
* defect in Sabre CPU and Simba bridge configuration
* which does not drain DMA write data stalled in
* PCI to PCI bridges (such as the DEC bridge) beyond
* Simba. This routine will setup the data structures
* to allow the pci_intr_wrapper to perform a manual
* drain data operation before passing the control to
* interrupt handlers of device drivers.
* return value:
* DDI_SUCCESS
* DDI_FAILURE if unable to create mapping
*/
static int
{
int simba_found = 0, pci_bridge_found = 0;
"map dev cfg reg for %s%d: @%s%d\n",
"no-dma-interrupt-sync"))
continue;
/* continue to search up-stream if not a PCI device */
if (vendor_id == 0xffff)
continue;
/* record the deepest pci device */
if (!pci_dip)
/* look for simba */
if (vendor_id == PCI_SIMBA_VENID) {
if (device_id == PCI_SIMBA_DEVID) {
simba_found = 1;
"\tFound simba\n");
continue; /* do not check bridge if simba */
}
}
/* look for pci to pci bridge */
"%s%d: can't get brdg cfg space for %s%d\n",
return (DDI_FAILURE);
}
== PCI_CLASS_BRIDGE) {
"\tFound PCI to xBus bridge\n");
pci_bridge_found = 1;
}
}
if (!pci_bridge_found)
return (DDI_SUCCESS);
return (DDI_SUCCESS);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* 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;
ino_p->ino_unclaimed_intrs = 0;
goto clear;
}
err_fmt_str = "%s%d: ino 0x%x blocked";
goto warn;
if (!pci_spurintr_msgs) { /* tomatillo errata #71 spurious mondo */
/* clear the pending state */
return (DDI_INTR_CLAIMED);
}
err_fmt_str = "!%s%d: spurious interrupt from ino 0x%x";
warn:
return (DDI_INTR_CLAIMED);
}
/*
* pci_intr_wrapper
*
* 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.
*/
extern uint64_t intr_get_time(void);
{
int i;
}
"pci_intr_wrapper: %s%d interrupt %d is disabled\n",
continue;
}
/*
* Account for time used by this interrupt. Protect against
* conflicting writes to ih_ticks from ib_intr_dist_all() by
* using atomic ops.
*/
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 (pci_spurintr(ipil_p));
ino_p->ino_unclaimed_intrs = 0;
ino_p->ino_claimed = 0;
/* Clear the pending state */
return (DDI_INTR_CLAIMED);
}
{
;
return (cdip);
}
static struct {
} pciintr_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 pciintr_ks_instance;
static char ih_devpath[MAXPATHLEN];
static char ih_buspath[MAXPATHLEN];
int
{
"fixed");
} else {
"disabled");
}
return (0);
}
int
{
int ret;
return (DDI_INTR_NOTFOUND);
}
volatile uint64_t *map_reg_addr;
if (mondo == 0)
goto fail1;
goto fail1;
/*
* Select cpu and program.
*
* Since there is no good way to always derive cpuid in
* pci_remove_intr for PCI_PULSE_INO (esp. for STARFIRE), we
* don't add (or remove) device weight for pulsed interrupt
* sources.
*/
cpu_id = intr_dist_cpuid();
*map_reg_addr; /* flush previous write */
goto done;
}
goto fail1;
goto fail2;
/* Sharing ino */
goto fail3;
}
/* add weight to the cpu that we are already targeting */
goto ino_done;
}
/* Store this global mondo */
/*
* Restore original interrupt handler
* and arguments in interrupt handle.
*/
if (ret != DDI_SUCCESS)
goto fail4;
/* Save the pil for this ino */
/* clear and enable interrupt */
/*
* Select cpu and compute weight, saving both for sharing and removal.
*/
#ifdef _STARFIRE
#endif /* _STARFIRE */
if (!ipil_list) {
*ino_p->ino_map_reg;
}
sizeof (pciintr_ks_template) / sizeof (kstat_named_t),
}
done:
return (DDI_SUCCESS);
if (ih_p->ih_config_handle)
return (DDI_FAILURE);
}
int
{
volatile uint64_t *map_reg_addr;
/*
* No weight was added by pci_add_intr for PCI_PULSE_INO
* because it is difficult to determine cpuid here.
*/
if (mondo == 0) {
"can't get mondo for ino %x\n", ino);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/* Translate the interrupt property */
if (mondo == 0) {
return (DDI_FAILURE);
}
if (!ino_p) {
if (r != DDI_SUCCESS)
return (r);
}
if (ipil_p->ipil_ih_size == 0) {
}
/* re-enable interrupt only if mapping register still shared */
*ino_p->ino_map_reg;
}
if (ino_p->ino_ipil_size == 0)
return (DDI_SUCCESS);
}
/*
* free the pci_inos array allocated during pci_intr_setup. the actual
* interrupts are torn down by their respective block destroy routines:
* cb_destroy, pbm_destroy, and ib_destroy.
*/
void
{
pci_p->pci_inos_len = 0;
}