Page 1 of 1
Getting the Interrupt Number
Posted: Fri Dec 17, 2004 12:16 pm
by purevoid
Hi,
How can I find the current interrupt number inside my ISR?
Basically I want to do something like:
Code: Select all
void isr_wrapper() {
uint8 int_num = get_current_interrupt_number();
actual_interrupt_handler(int_num);
}
void actual_interrupt_handler(uint8 num) {
switch (num) {
case 0: ... break;
case 1: ... break;
/* etc. 8?
}
}
However, the difference from the above is that I want to manage interrupt handling in OCaml (yes, we've got OCaml support in our kernel); I think it would be easier to handle interrupts in OCaml if could pass the interrupt number to it, otherwise it seems like a wasted effort.
Re:Getting the Interrupt Number
Posted: Fri Dec 17, 2004 12:40 pm
by HOS
basically you have to have a different label in each IDT entry (AFAIK)
here is what i use:
Code: Select all
;idt.inc
;Author: Josh Holtrop
;Date: 10/30/03
;Modified: 07/06/04
idtr:
dw 50*8-1 ;size of idt
dd IDT_V ;address of idt
%macro isr_label 1
isr_%1:
push eax
mov eax, %1
jmp isr_main
%endmacro
isr_label 0
isr_label 1
isr_label 2
isr_label 3
isr_label 4
isr_label 5
isr_label 6
isr_label 7
isr_label 8
isr_label 9
isr_label 10
isr_label 11
isr_label 12
isr_label 13
isr_label 14
isr_label 15
isr_label 16
isr_label 17
isr_label 18
isr_label 19
isr_label 20
isr_label 21
isr_label 22
isr_label 23
isr_label 24
isr_label 25
isr_label 26
isr_label 27
isr_label 28
isr_label 29
isr_label 30
isr_label 31
isr_label 32
isr_label 33
isr_label 34
isr_label 35
isr_label 36
isr_label 37
isr_label 38
isr_label 39
isr_label 40
isr_label 41
isr_label 42
isr_label 43
isr_label 44
isr_label 45
isr_label 46
isr_label 47
isr_label 48
isr_label 49
isr_main:
pusha
push ds
push es
push eax ;interrupt number
call _isr
add esp, 4
pop es
pop ds
popa
pop eax ;original saved eax
iret
this defines the labels, you would need to use these to populate the IDT to point to them
then define a C function called isr that takes an int argument (the interrupt number) and thats it...
i'm sure someone can point out how to improve on this or make it more elegant
ps this is nasm code...
Re:Getting the Interrupt Number
Posted: Fri Dec 17, 2004 12:45 pm
by Curufir
Two ways off the top of my head.
a) Use the stack values for the return address to look back at the previous instruction and interpret the machine code for the interrupt (0xCD 0xNN from memory). This will only work for software interrupts of course (Although with some finesse it could be extended to IRQs as well).
b) Just make your IDT entries equivalent to gotos in your ISR wrapper.
For example:
Code: Select all
void isr_wrapper() {
uint8 int_num = 0;
isr0:
int_num++;
isr1:
int_num++;
isr2:
int_num++;
....
actual_interrupt_handler(int_num);
}
Be aware that this will make some C people very annoyed though ;D.
c) (For fun and amusement) Create 256 identical GDT entries and arrange your IDT to load CS with the value of int_num*8.
Re:Getting the Interrupt Number
Posted: Fri Dec 17, 2004 12:53 pm
by purevoid
A simple question about NASM...
I'm creating an elf32 kernel image, so do I specify elf as output format, or should it be aout?
Re:Getting the Interrupt Number
Posted: Fri Dec 17, 2004 3:01 pm
by Candy
Curufir wrote:
a) Use the stack values for the return address to look back at the previous instruction and interpret the machine code for the interrupt (0xCD 0xNN from memory). This will only work for software interrupts of course (Although with some finesse it could be extended to IRQs as well).
No, you can't extend that to irq's by their nature. For software interrupts it's plausible though...
b) Just make your IDT entries equivalent to gotos in your ISR wrapper.
For example:
Code: Select all
void isr_wrapper() {
uint8 int_num = 0;
isr0:
int_num++;
isr1:
int_num++;
isr2:
int_num++;
....
actual_interrupt_handler(int_num);
}
Be aware that this will make some C people very annoyed though ;D.
No it won't... it's equivalent to switch fallthrough cases. PS: if you number them down the number you keep left is equal to the number of the interrupt instead of the inverse.
c) (For fun and amusement) Create 256 identical GDT entries and arrange your IDT to load CS with the value of int_num*8.
In a way, you *could* just push one register and dunk the int number in there, saving you 2k on GDT area. Or make an engine which dynamically writes code for interrupts as you register them
Re:Getting the Interrupt Number
Posted: Fri Dec 17, 2004 4:32 pm
by Curufir
Candy wrote:
Curufir wrote:
a) Use the stack values for the return address to look back at the previous instruction and interpret the machine code for the interrupt (0xCD 0xNN from memory). This will only work for software interrupts of course (Although with some finesse it could be extended to IRQs as well).
No, you can't extend that to irq's by their nature. For software interrupts it's plausible though...
It's something that sprang to mind when I considered reading the interrupt instruction. You'd do it for IRQs by reading the In Service Register on the PIC(s) instead of backtracking to the software interrupt. It'd be a real clumsy way of doing things, but I'm sure it's possible.
TBH I'm not sure why you'd want the information in the first place. If an ISR is to be run when an interrupt is called then hook it up in the IDT (An ISR should know which interrupt it corresponds to). Any interrupt that doesn't have a corresponding ISR should return an error. I don't see the actual interrupt number as useful information.
Re:Getting the Interrupt Number
Posted: Sat Dec 18, 2004 2:11 pm
by Colonel Kernel
Candy wrote:
Or make an engine which dynamically writes code for interrupts as you register them
I think there's actually a precedent for that in NT.
From Inside Windows NT 2nd Ed. (p. 89, 2nd paragraph):
The kernel provides a portable mechanism - a kernel control object called an interrupt object - that allows device drivers to register ISRs for their devices. An interrupt object contains all the information the kernel needs to associate a device ISR with a particular level of interrupt, including the address of the ISR, the IRQL at which the device interrupts, and the entry in the kernel's IDT with which the ISR should be associated. When an interrupt object is initialized, a few instructions of assembly language code, called the dispatch code, are stored in the object. When an interrupt occurs, this code is executed. This interrupt-object resident code calls the real interrupt dispatcher, passing it a pointer to the interrupt object. The interrupt object contains information this second dispatcher routine needs in order to locate and properly call the ISR the device driver provides. This two-step process is required because there is no way to pass a pointer to the interrupt object (or any other argument for that matter) on the initial dispatch since the initial dispatch is done by hardware.
Kinda neat. I think the mechanism they describe was also used in the Alpha AXP version of NT.
<edit>Unless of course you mean dynamically generate the entire handler, and not just the code that dispatches to it.
</edit>
Re:Getting the Interrupt Number
Posted: Sat Dec 18, 2004 2:46 pm
by purevoid
That dynamically generating dispatcher code sounds almost like what we'd need. How to do that, I have no idea...
But we've taken a slightly different approach, plus my understanding of interrupts was a little fuzzy before now.
For anyone interested in what's trying to be done, and maybe able to shed an extra bit of light on... we're writing our kernel in OCaml, and want to write interrupt handlers in OCaml too.
The biggest problem we face is calling into the OCaml function. Since we need to use a C callback function to call the actual OCaml function, I'm at a loss of how to put this information into the IDT, since you can't pass parameters easily.
Re:Getting the Interrupt Number
Posted: Sat Dec 18, 2004 3:24 pm
by Candy
purevoid wrote:
That dynamically generating dispatcher code sounds almost like what we'd need. How to do that, I have no idea...
Make it less dynamic and within the reasonability bounds. The more you constrain it the easier it gets.
My personal easiest example: restrict target address + interrupt number to predefined numbers
Code: Select all
/*
code in bytes is equivalent to push $x (5b); call $y (5b); add esp, 4 (3b); iret (1b);
*/
char code[] = { 0x68, 0x00, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x04, 0xCF, 0x00, 0x00};
char *newarray = (char *)malloc(16);
memcpy(newarray, code, 16);
newarray[1] = int_number;
newarray[6] = target_addr & 0xFF;
newarray[7] = (target_addr >> 8) & 0xFF;
newarray[8] = (target_addr >> 16) & 0xFF;
newarray[9] = (target_addr >> 24) & 0xFF;
This generates code to jump to $j with $i as parameter.
Code: Select all
But we've taken a slightly different approach, plus my understanding of interrupts was a little fuzzy before now.
For anyone interested in what's trying to be done, and maybe able to shed an extra bit of light on... we're writing our kernel in OCaml, and want to write interrupt handlers in OCaml too.
The biggest problem we face is calling into the OCaml function. Since we need to use a C callback function to call the actual OCaml function, I'm at a loss of how to put this information into the IDT, since you can't pass parameters easily.
[quote][/quote]
Use the code generator to generate an asm function to call C, and make some C wrapper that allows you to use it elegantly.
PS: no guarantees on the correctness of the code, you might want to ram some generated code through a disassembler first.
Re:Getting the Interrupt Number
Posted: Sat Dec 18, 2004 4:48 pm
by purevoid
I think this dynamic code generation isn't quite so hard afterall.
Anyways, here is some semi-pseudo code of how the ISR would look:
Code: Select all
/* asm */
pusha
push gs
push fs
push es
push ds
/* C */
callback(*closure, Val_unit); /* ignoring result */
/* asm */
pop ds
pop es
pop fs
pop gs
popa
iret
Where closure would be the only variable argument. Now to dynamically generate the code, I'd need to use some C code to get the address of callback, and Val_unit right? Insert those into the ASM, and then the address of closure? And generate a call instruction...
Hope I'm on the right track... and think I remember how C calling convention works.
And to finish off, I'd pass the address of the starting instruction to my set_vector function for IDT... so if I had a fixed size array for the char* representing the machine code, I'd just pass the address of an entry in this array, after filling in the necessary blanks in the char*?
Sounds a little too easy to be true...
Re:Getting the Interrupt Number
Posted: Sat Dec 18, 2004 5:25 pm
by purevoid
Been doing some reading of the Intel manuals, and here's the code I've come up with....
Code: Select all
char *isr_wrapper = {
/* pusha, push gs, fs, es, ds */
0x60, 0x0F, 0xA8, 0x0F, 0xA0, 0x06, 0x1E,
/* push parameter 2 onto stack */
0x68, 0x00, 0x00, 0x00, 0x00,
/* push parameter 1 onto stack */
0x68, 0x00, 0x00, 0x00, 0x00,
/* call function */
0x9A, 0x00, 0x00, 0x00, 0x00,
/* pop ds, es, fs, gs, popa, iret */
0x1F, 0x07, 0x0F, 0xA1, 0x0F, 0x09, 0x61, 0xCF
};
Where I'd replace the first lot of 0x00s with address of Val_unit, the second lot with address of *closure, and the last with address of the function `callback'.
It's just the call that I'm a little confused about, since there are a few different opcodes. From the Intel manual, I decided to use:
Opcode: 9A
cd, Instruction: CALL
ptr16:16, Description: Call far, absolute, address given in operand.
Assuming that's all correct, I'd just have to write the code to actually do all this work.
Re:Getting the Interrupt Number
Posted: Sun Dec 19, 2004 4:15 am
by Candy
purevoid wrote:
Been doing some reading of the Intel manuals, and here's the code I've come up with....
Code: Select all
char *isr_wrapper = {
/* pusha, push gs, fs, es, ds */
0x60, 0x0F, 0xA8, 0x0F, 0xA0, 0x06, 0x1E,
/* push parameter 2 onto stack */
0x68, 0x00, 0x00, 0x00, 0x00,
/* push parameter 1 onto stack */
0x68, 0x00, 0x00, 0x00, 0x00,
/* call function */
0x9A, 0x00, 0x00, 0x00, 0x00,
/* pop ds, es, fs, gs, popa, iret */
0x1F, 0x07, 0x0F, 0xA1, 0x0F, 0x09, 0x61, 0xCF
};
Where I'd replace the first lot of 0x00s with address of Val_unit, the second lot with address of *closure, and the last with address of the function `callback'.
It's just the call that I'm a little confused about, since there are a few different opcodes. From the Intel manual, I decided to use:
Opcode: 9A
cd, Instruction: CALL
ptr16:16, Description: Call far, absolute, address given in operand.
Assuming that's all correct, I'd just have to write the code to actually do all this work.
They are pretty much OK, all of them.
Except:
1. You indeed have the wrong jump. In 32-bits Pmode you need either a 16:32 jump (which you do have, but you need two more bytes), or an absolute jump with a 32-bit offset (as is done by FF /2). Page 3-65 in my manual.
2. You need to get those two arguments off the stack before returning.