in the name of zero

January 19, 2008

tying the 8259A pic to the ISRs

Filed under: hermetic studies

after i8259.c::init_IRQ() initializes the interrupt controller, it begins to
setup the interrupt descriptor table with it’s predefined ISR locations like so:

for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
	int vector = FIRST_EXTERNAL_VECTOR + i;
	if (i >= NR_IRQS)
		break;
	if (vector != SYSCALL_VECTOR)
		set_intr_gate(vector, interrupt[i]);
}

declared at hw_irq.h but defined and initialized via assembler constructs at entry.S, interrupt[] is an
array of ISR function pointers (to section irq_entries_start) that linux uses. i
removed the dwarf macros for clarity.

ENTRY(interrupt) interrupt[] address is in a .data section
.text
	
vector=0
ENTRY(irq_entries_start) // create a section called irq_entries_start in .text
.rept NR_IRQS
        ALIGN
1:      pushl $~(vector)	// save IRQ number, ~pt_regs->orig_eax
        jmp common_interrupt    // jump to common IRQ handling code
.data // now, back in our interrupt[] array at the .data section
        .long 1b		// place address of stub \"1\" in interrupt[i]
.text
vector=vector+1
.endr

then, common_interrupt is the one responsible for calling do_IRQ() which
will bring us back to the land of C. irq numbers are being passed in their two’s
complement form also.

putting it all together, the linux interrupt api

note: so this is basically intel 8259 pic specific.

in arch/i386/kernel/i8259.c, we could see a descriptor for the interrupt
controller being defined like so:

static struct irq_chip i8259A_chip = {
        .name           = "XT-PIC",		/* name used in /proc/interrupts */
        .mask           = disable_8259A_irq,	/* mask an interrupt source */
        .unmask         = enable_8259A_irq,	/* unmask an interrupt source */
        .mask_ack       = mask_and_ack_8259A, 	/* ack and mask an interrupt source */
};

struct irq_chip is defined at irq.h, there is a definitive explanation for each structure member there too. then, we also have the irq_desc structure which is a descriptor for each irq. it is also defined at irq.h. linux has a global array of irq_descriptor for the number of irqs defined by NR_IRQS. to get the descriptor for a specific irq is simply just a matter of adding an irqs interrupt number to get to the correct index in the irq_descriptor array.

disable_8259A() constructs a valid mask for the irq in question then checks to see if this irq is greater than 8. if it is, the mask is written to the slave pic, else, the mask is written to the master pic - remembering that irq 9-16 in the slave pic are cascaded to irq2 in the master pic.

enable_8259A() also follows the same pattern but it instead takes the one’s complement to turn on an interrupt bit.

mask_and_ack_8259A() sends a specific End Of Interrupt to the master PIC, or if irq > 8 to both the slave pic and the master pic in that order.

linux can tie a particular irq to the specific programmable interrupt controller with a specific interrupt code handler that would service the irq in question. in this writeup, linux assigns the first 16 interrupts to the i8529 pic with handle_level_irq() as the handler for them all.

we can see linux in action, setting the irq_descriptors and assigning the first 16 predefined irq to the intel 8259 pic in arch/i386/kernel/i8259.c::init_ISA_irqs(). specifically, in the following snippet:

for (i = 0; i < NR_IRQS; i++) {
	irq_desc[i].status = IRQ_DISABLED;
        irq_desc[i].action = NULL;
        irq_desc[i].depth = 1;
	
        if (i < 16) {
                 set_irq_chip_and_handler_name(i, &i8259A_chip,
                                               handle_level_irq, "XT");
	...
}

set_irq_chip_and_handler() is a wrapper to two functions, set_irq_chip() and __set_irq_handler(). the job of set_irq_chip() is to tie an irq descriptor to a specific interrupt chip and the job of __set_irq_handler() is simply to assign a handler to service the irq by assigning the handler address to the descriptor’s “handle_irq” member.

generic irq flow handlers:

handle_simple_irq
handle_level_irq
handle_edge_irq
handle_per_cpu_irq

they differ, primarly in how they handle an interrupt and successive interrupts
that happen at service time. all of the handlers eventually transfer control to
handle_IRQ_event() which will take
care of calling the list of irq action service routines.

[ installing irq action handlers ]

following irq/handle.c::handle_IRQ_event(), we could see it entering a
do-while() calling the handler of each action and then move to the next action
in the action list like so:

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
        ...
	do {
        	ret = action->handler(irq, action->dev_id);
                if (ret == IRQ_HANDLED)
                        status |= action->flags;
                retval |= ret;
                action = action->next;
        } while (action);
	...
}

the function responsible for installing these irq actions is found at irq/manage.c::setup_irq(). this function also registers an irq entry in /proc for the irq and it’s associated handler.

	...
	
                /* add new interrupt at end of irq queue */
                do {
                        p = &old->next;
                        old = *p;
                } while (old);
                shared = 1;
        }
	
        *p = new;
	
	...

struct irqaction is defined in interrupt.h as:

typedef irqreturn_t (*irq_handler_t)(int, void *);

struct irqaction {
        irq_handler_t handler;
        unsigned long flags;
        cpumask_t mask;
        const char *name;
        void *dev_id;
        struct irqaction *next;
        int irq;
        struct proc_dir_entry *dir;
};

going back to my original goal which is to understand how linux generates clock
ticks to make the scheduler work, i come upon mach-default/setup.c::time_init_hook() where the kernel installs an irqaction handler for irq0 - the timer interrupt.

static struct irqaction irq0 = { timer_interrupt, IRQF_DISABLED, CPU_MASK_NONE, “timer”, NULL, NULL};

void __init time_init_hook(void)
{
        setup_irq(0, &irq0);
}

that’s it for now, it’s time for me to enjoy my weekend.

Comments »

The URI to TrackBack this entry is: http://gnurbs.blogsome.com/2008/01/19/tying-the-8259a-pic-to-the-isrs/trackback/

No comments yet.

RSS feed for comments on this post.

Leave a comment

Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>


Get free blog up and running in minutes with Blogsome | Theme designs available here