1N/A/*
1N/A * Basic support for controlling the 8259 Programmable Interrupt Controllers.
1N/A *
1N/A * Initially written by Michael Brown (mcb30).
1N/A */
1N/A
1N/A#include <etherboot.h>
1N/A#include <pic8259.h>
1N/A
1N/A#ifdef DEBUG_IRQ
1N/A#define DBG(...) printf ( __VA_ARGS__ )
1N/A#else
1N/A#define DBG(...)
1N/A#endif
1N/A
1N/A/* Install a handler for the specified IRQ. Address of previous
1N/A * handler will be stored in previous_handler. Enabled/disabled state
1N/A * of IRQ will be preserved across call, therefore if the handler does
1N/A * chaining, ensure that either (a) IRQ is disabled before call, or
1N/A * (b) previous_handler points directly to the place that the handler
1N/A * picks up its chain-to address.
1N/A */
1N/A
1N/Aint install_irq_handler ( irq_t irq, segoff_t *handler,
1N/A uint8_t *previously_enabled,
1N/A segoff_t *previous_handler ) {
1N/A segoff_t *irq_vector = IRQ_VECTOR ( irq );
1N/A *previously_enabled = irq_enabled ( irq );
1N/A
1N/A if ( irq > IRQ_MAX ) {
1N/A DBG ( "Invalid IRQ number %d\n" );
1N/A return 0;
1N/A }
1N/A
1N/A previous_handler->segment = irq_vector->segment;
1N/A previous_handler->offset = irq_vector->offset;
1N/A if ( *previously_enabled ) disable_irq ( irq );
1N/A DBG ( "Installing handler at %hx:%hx for IRQ %d, leaving %s\n",
1N/A handler->segment, handler->offset, irq,
1N/A ( *previously_enabled ? "enabled" : "disabled" ) );
1N/A DBG ( "...(previous handler at %hx:%hx)\n",
1N/A previous_handler->segment, previous_handler->offset );
1N/A irq_vector->segment = handler->segment;
1N/A irq_vector->offset = handler->offset;
1N/A if ( *previously_enabled ) enable_irq ( irq );
1N/A return 1;
1N/A}
1N/A
1N/A/* Remove handler for the specified IRQ. Routine checks that another
1N/A * handler has not been installed that chains to handler before
1N/A * uninstalling handler. Enabled/disabled state of the IRQ will be
1N/A * restored to that specified by previously_enabled.
1N/A */
1N/A
1N/Aint remove_irq_handler ( irq_t irq, segoff_t *handler,
1N/A uint8_t *previously_enabled,
1N/A segoff_t *previous_handler ) {
1N/A segoff_t *irq_vector = IRQ_VECTOR ( irq );
1N/A
1N/A if ( irq > IRQ_MAX ) {
1N/A DBG ( "Invalid IRQ number %d\n" );
1N/A return 0;
1N/A }
1N/A if ( ( irq_vector->segment != handler->segment ) ||
1N/A ( irq_vector->offset != handler->offset ) ) {
1N/A DBG ( "Cannot remove handler for IRQ %d\n" );
1N/A return 0;
1N/A }
1N/A
1N/A DBG ( "Removing handler for IRQ %d\n", irq );
1N/A disable_irq ( irq );
1N/A irq_vector->segment = previous_handler->segment;
1N/A irq_vector->offset = previous_handler->offset;
1N/A if ( *previously_enabled ) enable_irq ( irq );
1N/A return 1;
1N/A}
1N/A
1N/A/* Send specific EOI(s).
1N/A */
1N/A
1N/Avoid send_specific_eoi ( irq_t irq ) {
1N/A DBG ( "Sending specific EOI for IRQ %d\n", irq );
1N/A outb ( ICR_EOI_SPECIFIC | ICR_VALUE(irq), ICR_REG(irq) );
1N/A if ( irq >= IRQ_PIC_CUTOFF ) {
1N/A outb ( ICR_EOI_SPECIFIC | ICR_VALUE(CHAINED_IRQ),
1N/A ICR_REG(CHAINED_IRQ) );
1N/A }
1N/A}
1N/A
1N/A/* Dump current 8259 status: enabled IRQs and handler addresses.
1N/A */
1N/A
1N/A#ifdef DEBUG_IRQ
1N/Avoid dump_irq_status (void) {
1N/A int irq = 0;
1N/A
1N/A for ( irq = 0; irq < 16; irq++ ) {
1N/A if ( irq_enabled ( irq ) ) {
1N/A printf ( "IRQ%d enabled, ISR at %hx:%hx\n", irq,
1N/A IRQ_VECTOR(irq)->segment,
1N/A IRQ_VECTOR(irq)->offset );
1N/A }
1N/A }
1N/A}
1N/A#endif
1N/A
1N/A/********************************************************************
1N/A * UNDI interrupt handling
1N/A * This essentially follows the defintion of the trivial interrupt
1N/A * handler routines. The text is assumed to locate in base memory.
1N/A */
1N/Avoid (*undi_irq_handler)P((void)) = _undi_irq_handler;
1N/Auint16_t volatile *undi_irq_trigger_count = &_undi_irq_trigger_count;
1N/Asegoff_t *undi_irq_chain_to = &_undi_irq_chain_to;
1N/Auint8_t *undi_irq_chain = &_undi_irq_chain;
1N/Airq_t undi_irq_installed_on = IRQ_NONE;
1N/A
1N/A/* UNDI entry point and irq, used by interrupt handler
1N/A */
1N/Asegoff_t *pxenv_undi_entrypointsp = &_pxenv_undi_entrypointsp;
1N/Auint8_t *pxenv_undi_irq = &_pxenv_undi_irq;
1N/A
1N/A/* Previous trigger count for undi IRQ handler */
1N/Astatic uint16_t undi_irq_previous_trigger_count = 0;
1N/A
1N/A/* Install the undi IRQ handler. Don't test as UNDI has not be opened.
1N/A */
1N/A
1N/Aint install_undi_irq_handler ( irq_t irq, segoff_t entrypointsp ) {
1N/A segoff_t undi_irq_handler_segoff = SEGOFF(undi_irq_handler);
1N/A
1N/A if ( undi_irq_installed_on != IRQ_NONE ) {
1N/A DBG ( "Can install undi IRQ handler only once\n" );
1N/A return 0;
1N/A }
1N/A if ( SEGMENT(undi_irq_handler) > 0xffff ) {
1N/A DBG ( "Trivial IRQ handler not in base memory\n" );
1N/A return 0;
1N/A }
1N/A
1N/A DBG ( "Installing undi IRQ handler on IRQ %d\n", irq );
1N/A *pxenv_undi_entrypointsp = entrypointsp;
1N/A *pxenv_undi_irq = irq;
1N/A if ( ! install_irq_handler ( irq, &undi_irq_handler_segoff,
1N/A undi_irq_chain,
1N/A undi_irq_chain_to ) )
1N/A return 0;
1N/A undi_irq_installed_on = irq;
1N/A
1N/A DBG ( "Disabling undi IRQ %d\n", irq );
1N/A disable_irq ( irq );
1N/A *undi_irq_trigger_count = 0;
1N/A undi_irq_previous_trigger_count = 0;
1N/A DBG ( "UNDI IRQ handler installed successfully\n" );
1N/A return 1;
1N/A}
1N/A
1N/A/* Remove the undi IRQ handler.
1N/A */
1N/A
1N/Aint remove_undi_irq_handler ( irq_t irq ) {
1N/A segoff_t undi_irq_handler_segoff = SEGOFF(undi_irq_handler);
1N/A
1N/A if ( undi_irq_installed_on == IRQ_NONE ) return 1;
1N/A if ( irq != undi_irq_installed_on ) {
1N/A DBG ( "Cannot uninstall undi IRQ handler from IRQ %d; "
1N/A "is installed on IRQ %d\n", irq,
1N/A undi_irq_installed_on );
1N/A return 0;
1N/A }
1N/A
1N/A if ( ! remove_irq_handler ( irq, &undi_irq_handler_segoff,
1N/A undi_irq_chain,
1N/A undi_irq_chain_to ) )
1N/A return 0;
1N/A
1N/A if ( undi_irq_triggered ( undi_irq_installed_on ) ) {
1N/A DBG ( "Sending EOI for unwanted undi IRQ\n" );
1N/A send_specific_eoi ( undi_irq_installed_on );
1N/A }
1N/A
1N/A undi_irq_installed_on = IRQ_NONE;
1N/A return 1;
1N/A}
1N/A
1N/A/* Safe method to detect whether or not undi IRQ has been
1N/A * triggered. Using this call avoids potential race conditions. This
1N/A * call will return success only once per trigger.
1N/A */
1N/A
1N/Aint undi_irq_triggered ( irq_t irq ) {
1N/A uint16_t undi_irq_this_trigger_count = *undi_irq_trigger_count;
1N/A int triggered = ( undi_irq_this_trigger_count -
1N/A undi_irq_previous_trigger_count );
1N/A
1N/A /* irq is not used at present, but we have it in the API for
1N/A * future-proofing; in case we want the facility to have
1N/A * multiple undi IRQ handlers installed simultaneously.
1N/A *
1N/A * Avoid compiler warning about unused variable.
1N/A */
1N/A if ( irq == IRQ_NONE ) {};
1N/A undi_irq_previous_trigger_count = undi_irq_this_trigger_count;
1N/A return triggered ? 1 : 0;
1N/A}