I'm working on a project that has tight boot time requirements. The targeted architecture is an IA-32 based processor running in 32 bit protected mode. One of the areas identified that can be improved is that the current system dynamically initializes the processor's IDT (interrupt descriptor table). Since we don't have any plug-and-play devices and the system is relatively static, I want to be able to use a statically built IDT.
However, this proving to be troublesome for the IA-32 arch since the 8 byte interrupt gate descriptors splits the ISR address. The low 16 bits of the ISR appear in the first 2 bytes of the descriptor, some other bits fill in the next 4 bytes, and then finally the last 16 bits of the ISR appear in the last 2 bytes.
I wanted to use a const array to define the IDT and then simply point the IDT register at it like so:
typedef struct s_myIdt { unsigned short isrLobits; unsigned short segSelector; unsigned short otherBits; unsigned short isrHibits; } myIdtStruct; myIdtStruct myIdt[256] = { { (unsigned short)myIsr0, 1, 2, (unsigned short)(myIsr0 >> 16)}, { (unsigned short)myIsr1, 1, 2, (unsigned short)(myIsr1 >> 16)},
etc.
Obviously this won't work as it is illegal to do this in C because myIsr is not constant. Its value is resolved by the linker (which can do only a limited amount of math) and not by the compiler.
Any recommendations or other ideas on how to do this?
Thanks,
You ran into a well known x86 wart. I don't believe the linker can stuff the address of your isr routines in the swizzled form expected by the IDT entry.
If you are feeling ambitious, you could create an IDT builder script that does something like this (Linux based) approach. I haven't tested this scheme and it probably qualifies as a nasty hack anyway, so tread carefully.
Step 1: Write a script to run 'nm' and capture the stdout.
Step 2: In your script, parse the nm output to get the memory address of all your interrupt service routines.
Step 3: Output a binary file, 'idt.bin' that has the IDT bytes all setup and ready for the LIDT instruction. Your script obviously outputs the isr addresses in the correct swizzled form.
Step 4: Convert his raw binary into an elf section with objcopy:
objcopy -I binary -O elf32-i386 idt.bin idt.elf
Step 5: Now idt.elf file has your IDT binary with the symbol something like this:
> nm idt.elf 000000000000000a D _binary_idt_bin_end 000000000000000a A _binary_idt_bin_size 0000000000000000 D _binary_idt_bin_start
Step 6: relink your binary including idt.elf. In your assembly stubs and linker scripts, you can refer to symbol _binary_idt_bin_start as the base of the IDT. For example, your linker script can place the symbol _binary_idt_bin_start at any address you like.
Be careful that relinking with the IDT section doesn't move anyting else in your binary, e.g. your isr routines. Manage this in your linker script (.ld file) by puting the IDT into it's own dedicated section.
---EDIT--- From comments, there seems to be confusion about the problem. The 32-bit x86 IDT expects the address of the interrupt service routine to be split into two different 16-bit words, like so:
31 16 15 0 +---------------+---------------+ | Address 31-16 | | +---------------+---------------+ | | Address 15-0 | +---------------+---------------+
A linker is thus unable to plug-in the ISR address as a normal relocation. So, at boot time, software must construct this split format, which slows boot time.
You could do something like this:
main.c:
#include <stdint.h> #include <stdio.h> void isr0 (); struct idt_entry { uint32_t idt_a; uint32_t idt_b; }; extern char idt_a_0; extern char idt_b_0; struct idt_entry idt0 = { (uint32_t)&idt_a_0, (uint32_t)&idt_b_0 }; int main () { printf ("isr0: %08x\n", &isr0); printf ("%08x\n", ((uint16_t*)&idt0)[0]); printf ("%08x\n", ((uint16_t*)&idt0)[1]); printf ("%08x\n", ((uint16_t*)&idt0)[2]); printf ("%08x\n", ((uint16_t*)&idt0)[3]); return 0; }
link.ld:
seg_selector = 1; other_bits = 2; isr0_lo = isr0 & 0xFFFF; isr0_hi = isr0 >> 16; idt_a_0 = (seg_selector << 16) | isr0_lo; idt_b_0 = (isr0_hi << 16) | other_bits;
isr0.c:
void isr0 () { }
Makefile:
CFLAGS=-m32 main: main.o isr0.o link.ld gcc -m32 -Wl,link.ld -o $@ $^ main.o: main.c isr0.o: isr0.c