Source to machdep/i386/intr.c


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
 *
 * @[email protected]
 * 
 * "Portions Copyright (c) 1999 Apple Computer, Inc.  All Rights
 * Reserved.  This file contains Original Code and/or Modifications of
 * Original Code as defined in and that are subject to the Apple Public
 * Source License Version 1.0 (the 'License').  You may not use this file
 * except in compliance with the License.  Please obtain a copy of the
 * License at http://www.apple.com/publicsource and read it before using
 * this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License."
 * 
 * @[email protected]
 */

/*
 * Copyright (c) 1992,1993 NeXT Computer, Inc.
 *
 * Interrupt handling routines.
 *
 * HISTORY
 *
 * 5 July 1993 ? at NeXT
 *	Removed software interrupt support.
 * 	Changed lower_ipl() to never raise masked_ipl.
 * 20 May 1993  Curtis Galloway at NeXT
 *	Added checking for phantom interrupts.
 * 26 August 1992 ? at NeXT
 *	Major rev for driverkit support.
 * 22 August 1992 ? at NeXT
 *	Added software interrupts.  Made all data structures static.
 * 20 Aug 1992	Joe Pasqua
 *	Added intr_enable_irq/disable_irq routines based on the enableMask.
 * 24 June 1992 ? at NeXT
 *	Rewritten to implement 'soft spls'.
 * 1 June 1992 ? at NeXT
 *	Created.
 */
 
#import <mach/mach_types.h>

#import <machdep/i386/intr_exported.h>
#import <machdep/i386/intr_internal.h>
#import <machdep/i386/intr_inline.h>
#import <machdep/i386/cpu_inline.h>

static intr_dispatch_t	dispatch_table[INTR_NIRQ];
static intr_irq_mask_t	ipl_mask[INTR_NIPL];
static intr_dispatch_t	*defer_table[INTR_NIPL];

static int		current_ipl, masked_ipl;
static intr_irq_mask_t	current_irq_mask, disabled_irq_mask;
static intr_irq_mask_t	current_elcr;

/*
 * Send an EOI (end of interrupt) to both PICs.
 */
static inline
void
send_eoi(
    void
)
{
    send_eoi_command((intr_ocw2_t) {
			0,		/* no level	*/
			0,		/* must be	*/
			TRUE,		/* EOI		*/
			FALSE,		/* non-specific	*/
			FALSE		/* no rotation	*/
		    });
}

static inline
void
set_elcr(
    intr_irq_mask_t		mask
)
{
    union {
	intr_irq_mask_t		full;
	struct {
	    unsigned short
				half	:8,
					:8;
	} master;
	struct {
	    unsigned short
					:8,
				half	:8;
	} slave;
    } new_mask;

    new_mask.full.mask = mask.mask;
    
    if (new_mask.full.mask != current_elcr.mask) {
    	current_elcr = new_mask.full;

	set_master_elcr((intr_elcr_t) {
					    new_mask.master.half });
    
	set_slave_elcr((intr_elcr_t) {
					    new_mask.slave.half });
    }
}

/*
 * Set the interrupt masks of both PICs
 * according to the irq mask.
 */
static inline
void
set_irq_mask(
    intr_irq_mask_t		mask
)
{
    union {
	intr_irq_mask_t		full;
	struct {
	    unsigned short
				half	:8,
					:8;
	} master;
	struct {
	    unsigned short
					:8,
				half	:8;
	} slave;
    } new_mask;
    
    new_mask.full.mask = mask.mask | disabled_irq_mask.mask;

    if (new_mask.full.mask != current_irq_mask.mask) {
	current_irq_mask = new_mask.full;
    
	set_master_mask((intr_ocw1_t) {
					    new_mask.master.half });
	
	set_slave_mask((intr_ocw1_t) {
					    new_mask.slave.half });
    }
}

/*
 * Set the irq mask according to
 * the ipl.
 */
static inline
int
set_masked_ipl(
    int		ipl
)
{
    int		old_ipl = masked_ipl;

    if (ipl != masked_ipl) {
	set_irq_mask(ipl_mask[ipl]);
	masked_ipl = ipl;
    }
    
    return (old_ipl);
}

/*
 * Set the 'soft' ipl.
 */
static inline
int
set_ipl(
    int		ipl
)
{
    int		old_ipl = current_ipl;
	
    current_ipl = ipl;
    
    return (old_ipl);
}

/*
 * Clear the interrupt flag, and
 * return the original state of
 * the flag.
 */
boolean_t
intr_disbl(
    void
)
{
    unsigned int	efl;

    efl = eflags();
	    
    cli();
	    
    return ((efl & EFL_IF) != 0);
}

/*
 * Set or clear the interrupt
 * flag, and return the original
 * state of the flag.
 */
boolean_t
intr_enbl(
    boolean_t	enable
)
{
    unsigned int	efl;

    efl = eflags();
	    
    if (enable) sti(); else cli();
	
    return ((efl & EFL_IF) != 0);
}

static inline
void
lower_masked_ipl(
    int			ipl
)
{
    if (ipl < masked_ipl)
    	(void) set_masked_ipl(ipl);
}

static
void
lower_ipl(
    int			ipl,
    int			old_ipl
)
{
    intr_dispatch_t	**d, *i;
    
    for (d = &defer_table[old_ipl]; d > &defer_table[ipl]; d--)
	if (i = *d) {
	    *d = 0;

	    (void) set_ipl(i->ipl);
	    
	    sti();
		(*i->routine)(i->which, 0, ipl); 
	    cli();
	}
	    
    (void) set_ipl(ipl);
    
    lower_masked_ipl(ipl);
}

/*
 * External 'well known' routines.
 */

inline
int
splx(
    int		ipl
)
{
    int		old_ipl;

    cli();

    old_ipl = set_ipl(ipl);
    if (ipl < old_ipl)
	lower_ipl(ipl, old_ipl);

    sti();
    
    return (old_ipl);
}

#define	DEFINE_SPL(name, ipl)	\
int spl##name(			\
    void			\
)				\
{				\
    return (splx(ipl));		\
}

#define DEFINE_SPLNOP(name)	\
int spl##name(			\
    void			\
)				\
{				\
    return (current_ipl);	\
}

DEFINE_SPL(0,		INTR_IPL0)

DEFINE_SPL(1,		INTR_IPL1)
DEFINE_SPLNOP(tty)

DEFINE_SPL(2,		INTR_IPL2)

DEFINE_SPL(3,		INTR_IPL3)
DEFINE_SPLNOP(net)
DEFINE_SPLNOP(vm)
DEFINE_SPLNOP(imp)
DEFINE_SPLNOP(bio)
DEFINE_SPL(device,	INTR_IPL3)

DEFINE_SPL(4,		INTR_IPL4)

DEFINE_SPL(5,		INTR_IPL5)

DEFINE_SPL(6,		INTR_IPL6)
DEFINE_SPL(dma,		INTR_IPL6)
DEFINE_SPL(usclock,	INTR_IPL6)
DEFINE_SPL(sched,	INTR_IPL6)
DEFINE_SPL(clock,	INTR_IPL6)

DEFINE_SPL(7,		INTR_IPL7)
DEFINE_SPL(high,	INTR_IPL7)

int
spln(
    int		ipl
)
{
    return (splx(ipl));
}

int
ipltospl(
    int		ipl
)
{
    return (ipl);
}

/*
 * Return the current ipl.
 */
int
curipl(
    void
)
{
    return (current_ipl);
}

/*
 * Initialization templates.
 */

#define MASTER_ICW1	((intr_icw1_t) {				\
			    TRUE,		/* IC4 needed	*/	\
			    FALSE,		/* cascaded 	*/	\
			    0,			/* not used	*/	\
			    INTR_ICW1_EDGE_TRIG,			\
			    1			/* must be	*/	\
			})
#define MASTER_ICW2	((intr_icw2_t) {				\
			    INTR_VECT_OFF	/* index in IDT	*/	\
			})
#define MASTER_ICW3	((intr_icw3m_t) {				\
			    INTR_MASK_SLAVE	/* slave inputs */	\
			})
#define MASTER_ICW4	((intr_icw4_t) {				\
			    INTR_ICW4_8086_MODE,			\
			    FALSE,		/* no auto EOI */	\
			    INTR_ICW4_NONBUF,				\
			    FALSE		/* no SFN mode */	\
			})

#define SLAVE_ICW1	((intr_icw1_t) {				\
			    TRUE,		/* IC4 needed	*/	\
			    FALSE,		/* cascaded 	*/	\
			    0,			/* not used	*/	\
			    INTR_ICW1_EDGE_TRIG,			\
			    1			/* must be	*/	\
			})
#define SLAVE_ICW2	((intr_icw2_t) {				\
			    INTR_VECT_OFF + 8	/* index in IDT	*/	\
			})
#define SLAVE_ICW3	((intr_icw3s_t) {				\
			    INTR_SLAVE_IRQ	/* slave id	*/	\
			})
#define SLAVE_ICW4	((intr_icw4_t) {				\
			    INTR_ICW4_8086_MODE,			\
			    FALSE,		/* no auto EOI */	\
			    INTR_ICW4_NONBUF,				\
			    FALSE		/* no SFN mode */	\
			})

/*
 * Called early during system
 * initialization.  Sets up the
 * PICs, sets the ipl to IPLHI,
 * and masks all irqs.
 */
void
intr_initialize(
    void
)
{
    intr_irq_mask_t	*m;
    int			i, mask;

    cli();

    initialize_master(	MASTER_ICW1,
    			MASTER_ICW2,
			MASTER_ICW3,
			MASTER_ICW4);

    initialize_slave(	SLAVE_ICW1,
    			SLAVE_ICW2,
			SLAVE_ICW3,
			SLAVE_ICW4);

    mask = INTR_MASK_ALL;
    for (i = 0, m = ipl_mask; i++ < INTR_NIPL; m++)
	m->mask = mask;
	
    disabled_irq_mask.mask = INTR_MASK_NONE;

    set_masked_ipl(INTR_IPLHI);

    set_ipl(INTR_IPLHI);
    
    sti();
}

/*
 * Register an ISR for an irq at
 * the ipl.  Returns TRUE on success,
 * FALSE on failure.  Failure includes
 * trying to overwrite an existing ISR.
 */
boolean_t
intr_register_irq(
    int			irq,
    intr_handler_t	routine,
    unsigned int	which,
    int			ipl
)
{
    intr_irq_mask_t	*m;
    int			i, mask;
    boolean_t		e;

    if (irq < 0 || irq >= INTR_NIRQ || irq == INTR_SLAVE_IRQ)
	return (FALSE);

    if (ipl < 0 || ipl >= INTR_NIPL)
	return (FALSE);

    if (dispatch_table[irq].routine)
	return (FALSE);

    e = intr_disbl();

    dispatch_table[irq].which	= which;
    dispatch_table[irq].routine	= routine;
    dispatch_table[irq].ipl	= ipl;

    mask = INTR_MASK_IRQ(irq);
    for (i = 0, m = ipl_mask; i < INTR_NIPL; i++, m++) {
	if (i < ipl)
	    m->mask &= ~mask;
	else
	    m->mask |= mask;
    }
    
    disabled_irq_mask.mask |= mask;

    set_irq_mask(ipl_mask[masked_ipl]);
    
    (void) intr_enbl(e);
    
    return (TRUE);
}

boolean_t
intr_unregister_irq(
    int			irq
)
{
    intr_irq_mask_t	*m;
    int			i, mask;
    boolean_t		e;

    if (irq < 0 || irq >= INTR_NIRQ || irq == INTR_SLAVE_IRQ)
	return (FALSE);

    if (!dispatch_table[irq].routine)
	return (FALSE);

    e = intr_disbl();

    dispatch_table[irq].which	= 0;
    dispatch_table[irq].routine	= 0;
    dispatch_table[irq].ipl	= 0;

    mask = INTR_MASK_IRQ(irq);
    for (i = 0, m = ipl_mask; i < INTR_NIPL; i++, m++) {
	m->mask |= mask;
    }

    set_irq_mask(ipl_mask[masked_ipl]);
    
    (void) intr_enbl(e);
    
    (void) intr_change_mode(irq, FALSE);
    
    return (TRUE);
}

boolean_t
intr_enable_irq(
    int		irq
)
{
    boolean_t	e;

    if (irq < 0 || irq >= INTR_NIRQ || irq == INTR_SLAVE_IRQ)
	return (FALSE);

    e = intr_disbl();

    disabled_irq_mask.mask &= ~INTR_MASK_IRQ(irq);
    set_irq_mask(ipl_mask[masked_ipl]);

    (void) intr_enbl(e);
    
    return (TRUE);
}

boolean_t
intr_disable_irq(
    int		irq
)
{
    boolean_t	e;

    if (irq < 0 || irq >= INTR_NIRQ || irq == INTR_SLAVE_IRQ)
	return (FALSE);

    e = intr_disbl();

    disabled_irq_mask.mask |= INTR_MASK_IRQ(irq);
    set_irq_mask(ipl_mask[masked_ipl]);
 
    (void) intr_enbl(e);
    
    return (TRUE);
}

boolean_t
intr_change_ipl(
    int		irq,
    int		ipl
)
{
    intr_irq_mask_t	*m;
    int			i, mask;
    boolean_t		e;

    if (irq < 0 || irq >= INTR_NIRQ || irq == INTR_SLAVE_IRQ)
	return (FALSE);

    if (ipl < 0 || ipl >= INTR_NIPL)
	return (FALSE);

    if (dispatch_table[irq].routine == 0)
	return (FALSE);

    e = intr_disbl();

    dispatch_table[irq].ipl	= ipl;

    mask = INTR_MASK_IRQ(irq);
    for (i = 0, m = ipl_mask; i < INTR_NIPL; i++, m++) {
	if (i < ipl)
	    m->mask &= ~mask;
	else
	    m->mask |= mask;
    }

    set_irq_mask(ipl_mask[masked_ipl]);
    
    (void) intr_enbl(e);
    
    return (TRUE);
}

boolean_t
intr_change_mode(
    int		irq,
    boolean_t	level_trig
)
{
    intr_irq_mask_t	reg;
    boolean_t		e;
#define _T_	TRUE
#define _F_	FALSE
    static boolean_t	valid_irq[INTR_NIRQ] = {
			    _F_, _F_, _F_, _T_, _T_, _T_, _T_, _T_,
			    _F_, _T_, _T_, _T_, _T_, _F_, _T_, _T_
			};
#undef _T_
#undef _F_
    
    if (!eisa_present())
    	return (FALSE);

    if (irq < 0 || irq >= INTR_NIRQ || !valid_irq[irq])
	return (FALSE);
	
    e = intr_disbl();
    
    reg = current_elcr;
    
    if (level_trig)
    	reg.mask |= INTR_MASK_IRQ(irq);
    else
    	reg.mask &= ~INTR_MASK_IRQ(irq);
	
    set_elcr(reg);
    
    (void) intr_enbl(e);
    
    return (TRUE);
}

struct {
    unsigned int	intr;
    unsigned int	defer;
    unsigned int	phantom;
} intr_cnt;

/*
 * The system interrupt handler.
 */
void
intr_handler(
    void			*_state
)
{
    thread_saved_state_t	*state = (thread_saved_state_t *)_state;
    int				irq = state->trapno - INTR_VECT_OFF;
    intr_dispatch_t		*i = &dispatch_table[irq];
    int				old_masked_ipl;

    intr_cnt.intr++;

    /*
     * Check for phantom interrupt.
     */
    if (((irq == INTR_MASTER_PHANTOM_IRQ && 
		(get_master_isr() & INTR_PHANTOM_IRQ_MASK) == 0)) ||
	((irq == INTR_SLAVE_PHANTOM_IRQ &&
		(get_slave_isr() & INTR_PHANTOM_IRQ_MASK) == 0)) ) {
	 intr_cnt.phantom++;
	 return;
    }

    /*
     * Mask this interrupt before
     * acknowledging so that level
     * triggered inputs work.
     */
    old_masked_ipl = set_masked_ipl(i->ipl);

    /*
     * Acknowledge by sending
     * an EOI command to the PICs.
     */
    send_eoi();  

    /*
     * Leave this interrupt
     * disabled if it isn't
     * currently registered.
     * N.B.: This should never
     * happen.
     */
    if (i->routine == 0)	
	printf("intr: dropped IRQ %d\n", irq);
    else
    /*
     * If this interrupt isn't
     * disallowed via spl(),
     * handle it now.
     */
    if (current_ipl < i->ipl) {
	int	old_ipl		= set_ipl(i->ipl);
		
	sti(); (*i->routine)(i->which, state, old_ipl); cli();
	
	(void) set_ipl(old_ipl);
	(void) set_masked_ipl(old_masked_ipl);
    }
    /*
     * Otherwise, leave it masked
     * and defer it until later.
     */
    else {
	intr_cnt.defer++;
		
    	defer_table[i->ipl] = i;
    }
}