Page 1 of 2

I can't get my interrupt code to work. What am I missing?

Posted: Fri Aug 31, 2007 12:48 pm
by vhg119
I've read the manual numerous times as well as tons of tutorials and forum posts on the matter.

I think I must have some typo or glitch in my logic because this code is driving me nuts.

The problem is that as soon as I enable interrupts, I get a ton of these errors:

Code: Select all

00001193930i[CPU0 ] BxError: instruction with opcode=0xff
00001193930i[CPU0 ] mod was c0, nnn was 7, rm was 7
00001193930i[CPU0 ] WARNING: Encountered an unknown instruction (signalling illegal instruction)
Eventually it'll triple fault and reset itself. I'm doing this all in Bochs by the way.

Could someone with a fresh set of eyes take a look at my code and let me know if you spot something weird?

My ISRs are defined like so:

Code: Select all

...
isr9:
	cli
	push $0
	push $9
	jmp isr_common_stub

isr10:
	cli
	push $10
	jmp isr_common_stub
...
Here is my isr_common_stub which each ISR jumps to:

Code: Select all

isr_common_stub:
        pusha
	pushw %ds			# Push all user segment selectors
	pushw %es
	pushw %fs
	pushw %gs
	movw $0x10, %ax			# User kernel data segment selector
	movw %ax, %ds
	movw %ax, %es
	movw %ax, %fs
	movw %ax, %gs
	movl %esp, %eax			# Push the address of ESP as a parameter so that the 
	pushl %eax			# Fault handler function can access the REG structure
	movl fault_handler, %eax
	call %eax			# Call the fault handler
	popl %eax
	popw %gs
	popw %fs
	popw %es
	popw %ds
	popa
	addl $8, %esp			# Cleans up pushed error code and Interrupt number
	iret				# CPU will pop off CS, EIP, EFLAGS, SS, and ESP automatically
Each ISR calls my fault_handler, which is written in C code. Right now it doesn't do anything. It justs loops infinitely:

Code: Select all

void fault_handler(struct REGS *r)
{
	for(;;);
}


First, I remap the IRQs:

Code: Select all

outb(0x20, 0x11);
outb(0xA0, 0x11);
outb(0x21, 0x20);
outb(0xA1, 0x28);
outb(0x21, 0x04);
outb(0xA1, 0x02);
outb(0x21, 0x01);
outb(0xA1, 0x01);
outb(0x21, 0x0);
outb(0xA1, 0x0);
Then, I install the IDT:

Code: Select all

struct IDT * idt_dest = IDT_ADDR;		     // IDT starts at this address
struct IDT_PTR idtr = {0x1000, IDT_ADDR};	// lidt will load this structure

// 		   Selector,    Offset,   Flags,      IDT
idt_entry_set_values(0x08, (unsigned)isr0, 0x8E, idt_dest++);

...

idt_entry_set_values(0x08, (unsigned)isr31, 0x8E, idt_dest++);
idt_entry_set_values(0x08, (unsigned)irq0, 0x8E, idt_dest++);

...

idt_entry_set_values(0x08, (unsigned)irq15, 0x8E, idt_dest);

 __asm__ ("lidt (%0)\n\t":: "r" (&idtr))

__asm__ __volatile__ ("sti\n\t");
Here's what my idt_entry_set_values function looks like:

Code: Select all

void idt_entry_set_values(unsigned short selector, unsigned int offset, unsigned char flags, struct IDT* e)
{
	e->selector = selector;
	e->offset_low = (offset & 0xFFFF);
	e->offset_high = (offset >> 16) & 0xFFFF;
	e->always0 = 0;
	e->flags = flags;
}
And here's my struct IDT:

Code: Select all

struct IDT
{
	unsigned short offset_low;	
	unsigned short selector;	// Selects GDT descriptor
	unsigned char always0;		// Always set this to zero
	unsigned char flags;
	unsigned short offset_high;
} __attribute__((packed));

Posted: Sat Sep 01, 2007 2:45 am
by JamesM
Couple of things:

1) How are your IRQs handled? you're using Bran's basecode, and he has a seperate irq_common_stub.

2) Have you tried it without remapping the PIC first? you can get software interrupts working fine without that done. (You may be remapping then recieving spurious interrupts you haven't handled)

EDIT: Why is your limit 0x1000? 256*8 = 2048 = 0x800. And, the limit should be the last valid byte, so 2048-1 = 2047.

I always work it out programmatically:

Code: Select all

idtPtr.limit = sizeof(IdtEntry) * 256 - 1;
idtPtr.base  = (u32int)&idtEntry;
EDIT2: another bug, this one probably is the one causing the problem : you are not zero'ing the entire array. If (which I doubt) you have all 256 idt entries mapped using idt_set_gate then this does not apply.

The cpu may be hitting a duff isr entry. It does not mind NULL entries, but garbage (rightly) confuses it.

Something like:

Code: Select all

memset(&iptr, 0, sizeof(idt_entry)*256);
Would work. (Sorry, I'm editing my post so I dont get a simulaneous look at your post, so can't remember your var names! :P

Posted: Sat Sep 01, 2007 5:13 am
by jerryleecooper
Is it true that disabling the IF bit of the eflags is unnecessary when an interrupt occurs? Because It is already doing it?

Posted: Sat Sep 01, 2007 5:19 am
by bluecode
jerryleecooper wrote:Is it true that disabling the IF bit of the eflags is unnecessary when an interrupt occurs? Because It is already doing it?
Depends on the Gate Descriptor Type. A Interrupt Gate clears IF, a Trap Gate does not.

Re: I can't get my interrupt code to work. What am I missin

Posted: Sat Sep 01, 2007 11:45 am
by vhg119
isr_common_stub:
pusha
pushw %ds # Push all user segment selectors
pushw %es
pushw %fs
pushw %gs
movw $0x10, %ax # User kernel data segment selector
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movl %esp, %eax # Push the address of ESP as a parameter so that the
pushl %eax # Fault handler function can access the REG structure
movl fault_handler, %eax
call %eax # Call the fault handler

popl %eax
popw %gs
popw %fs
popw %es
popw %ds
popa
addl $8, %esp # Cleans up pushed error code and Interrupt number
iret # CPU will pop off CS, EIP, EFLAGS, SS, and ESP automatically
Thanks for the reply guys. The part I've hilighted in bold was the part that was giving me all the errors. I changed it to

Code: Select all

call fault_handler
I'm not sure why Bran's tutorial was loading a register with the address and calling it with the register rather than calling it directly.

Could someone explain?

Now, it's not giving me errors anymore. My fault_handler is running and prints to screen each time it is fired. I just need to modify it so I know exactly which interrupt is being fired.

Posted: Mon Sep 03, 2007 11:04 am
by vhg119
JamesM wrote: EDIT: Why is your limit 0x1000? 256*8 = 2048 = 0x800. And, the limit should be the last valid byte, so 2048-1 = 2047.

I always work it out programmatically:

Code: Select all

idtPtr.limit = sizeof(IdtEntry) * 256 - 1;
idtPtr.base  = (u32int)&idtEntry;
Yeah, I know. I just arbitrarily set it to 1 Page. I'm confused by your calculation. I thought that the limit was the length of the IDT in bytes. So, wouldn't a 2048 byte IDT's limit still be 2048?

Does calling IRET automatically perform a "STI". The reason I ask is because Bran doesn't explicity issue a STI when returning from the interrupt handler.

And... If someone can explain the post above it would be greatly appreciated.

Posted: Mon Sep 03, 2007 11:47 am
by jnc100
vhg119 wrote:Does calling IRET automatically perform a "STI". The reason I ask is because Bran doesn't explicity issue a STI when returning from the interrupt handler.
When a ISR is run, the cpu pushes (amongst other things) an image of eflags onto the stack for the privilege level the isr will run at. This image contains the value of IF before the isr is run. Calling iret pops this off, thus restoring IF to its previous value (enabling it if it was already enabled, disabling it otherwise). See the Intel manuals, vol 2A on iret, and 3A:5.12.1 on Exception and Interrupt Handler Procedures.

Whether you need to issue cli at the start of an interrupt handler is another issue. If an interrupt gate is used, then IF is cleared for you by the processor. If you use a trap gate then it isn't. Generally, though, you would use a trap gate to define an isr which is preemptible, so doing a cli kind of defeats the point. I don't know why the tutorial does use it in isrs.

Regards,
John.

Posted: Mon Sep 03, 2007 4:53 pm
by vhg119
jnc100 wrote:
vhg119 wrote:Does calling IRET automatically perform a "STI". The reason I ask is because Bran doesn't explicity issue a STI when returning from the interrupt handler.
When a ISR is run, the cpu pushes (amongst other things) an image of eflags onto the stack for the privilege level the isr will run at. This image contains the value of IF before the isr is run. Calling iret pops this off, thus restoring IF to its previous value (enabling it if it was already enabled, disabling it otherwise). See the Intel manuals, vol 2A on iret, and 3A:5.12.1 on Exception and Interrupt Handler Procedures.

Whether you need to issue cli at the start of an interrupt handler is another issue. If an interrupt gate is used, then IF is cleared for you by the processor. If you use a trap gate then it isn't. Generally, though, you would use a trap gate to define an isr which is preemptible, so doing a cli kind of defeats the point. I don't know why the tutorial does use it in isrs.

Regards,
John.
Great explanation. I totally understand that part now. I'm still confused why Bran moves fault_handler to a register and uses that to call instead of calling it directly.

Posted: Mon Sep 03, 2007 5:34 pm
by elderK
Aye, It is as Bluecode said, It depends on the Gate.

Interrupt Gates will clear the IF Flag, during execution of the Handler and on exit, IRET will restore the IF flag to what it was previously (Entering a handler and IRETing from one also toggles other bits in the EFLAGS register but, I can't remember exactly which).

Trap Gates do NOT clear the IF flag, but otherwise function identically to Interrupt Gates.

So:
- If Hardware Interrupts are enabled before execution of the handler, You don't need to disable/renable interrupts explicitly. That is handled in hardware.

- If you are using a trap gate AND you need to work with Hardware interrupts disabled for a moment then yes, You must explicitly disable interrupts. You don't have to reenable them as EFLAGS will be popped when you return from the handler, effectively restoring IF to what it was *before* executing the handler.

Hope this clears things up a little, if not, try reading Section 5.11 in the Intel Software Developers Manual, Volume 3A.

~Zeii, 32bit Alchemist.

Posted: Mon Sep 03, 2007 6:54 pm
by vhg119
Once again, great explanation. I got that part already though.

The other thing I was confused about was why Bran moved fault_handler to eax and then issuing a 'call eax' rather than just doing 'call fault_handler'.

Is there a reason for doing this?

Code: Select all

movw %ax, %gs
movl %esp, %eax # Push the address of ESP as a parameter so that the
pushl %eax # Fault handler function can access the REG structure
movl fault_handler, %eax
call %eax # Call the fault handler
popl %eax
popw %gs
popw %fs
popw %es 

Posted: Mon Sep 03, 2007 7:38 pm
by elderK
Well, I don't know the specific reason why Bran decided to do this but... the reason it isn't working is simply because your translation of his Intel-syntax Assembly to AT&T is incorrect.

Code: Select all

  Intel:

  mov eax, _fault_handler
  call eax

  ==

  mov eax, _fault_handler
  call near eax

 AT&T:
 movl _fault_handler, %eax
 call *%eax
This might be wrong, well, the call *%eax as it is a correction I have made, based off what I assume to be an error in this reference: http://sig9.com/articles/att-syntax.

Also, I can't remember exactly how you get the address of a variable (or function, in this case) in AT&T, it's been awhile. I'm pretty sure just loading the name will be okay but you'll really need to experiment to make sure.

~Zeii, 32bit Alchemist.

Posted: Mon Sep 03, 2007 9:44 pm
by vhg119
zeii wrote: This might be wrong, well, the call *%eax as it is a correction I have made, based off what I assume to be an error in this reference: http://sig9.com/articles/att-syntax.
Wow, thanks for this. I originally started out learning NASM. But since GCC used GAS, I decided to translate all my code to assemble with AS. This is a great resource that you've linked me to.

I just called the fault_handler directly without assigning it to a register first and it works fine. All I wanted to know was whether or not he was using some trick that I didn't know about.

Posted: Mon Sep 03, 2007 10:40 pm
by vhg119
vhg119 wrote:
JamesM wrote: EDIT: Why is your limit 0x1000? 256*8 = 2048 = 0x800. And, the limit should be the last valid byte, so 2048-1 = 2047.

I always work it out programmatically:

Code: Select all

idtPtr.limit = sizeof(IdtEntry) * 256 - 1;
idtPtr.base  = (u32int)&idtEntry;
Yeah, I know. I just arbitrarily set it to 1 Page. I'm confused by your calculation. I thought that the limit was the length of the IDT in bytes. So, wouldn't a 2048 byte IDT's limit still be 2048?

Does calling IRET automatically perform a "STI". The reason I ask is because Bran doesn't explicity issue a STI when returning from the interrupt handler.

And... If someone can explain the post above it would be greatly appreciated.
Sorry, I just RTM again. Size of segment minus 1. You're right.

Posted: Tue Sep 04, 2007 1:10 am
by pcmattman
AT&T:

Code: Select all

call _fault_handler
I did the conversion from Intel to AT&T syntax on all Bran's code - the ISR stub is in http://mattise.cvs.sourceforge.net/matt ... iew=markup and the IRQ stub is in http://mattise.cvs.sourceforge.net/matt ... iew=markup

You can safely remove the task save/restore stuff if you don't need it.

Posted: Tue Sep 04, 2007 12:15 pm
by vhg119
pcmattman wrote:AT&T:

Code: Select all

call _fault_handler
I did the conversion from Intel to AT&T syntax on all Bran's code - the ISR stub is in http://mattise.cvs.sourceforge.net/matt ... iew=markup and the IRQ stub is in http://mattise.cvs.sourceforge.net/matt ... iew=markup

You can safely remove the task save/restore stuff if you don't need it.
Thank you. I see that you just called the handler directly also. Thanks for posting the source to your project. You're several (thousand) steps ahead of me. But just enough for me to still understand what you're doing. Reading your source will probably help clear a lot of things up for me, unlike Minix or Linux which looks like alien language to me.

Vince