Im ieee
Im ieee

Reputation: 469

Interrupt handler does not work on a real computer

I'm writing a bootloader-like program that changes default interrupt handler for the keyboard interrupt (int 0x9). It works on bochs and qemu, but not on a real computer, where it prints 'A' only once and then it does not react to pressing any key. (It should print the char at least twice, one time for pressing and one for releasing). How can I make the program work correctly? I'm using gcc. Kernel code:

asm(".code16gcc\n");

typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef struct __attribute__((__packed__)) FullAddr{
    ushort offset;
    ushort seg;
} FullAddr;

#define MAIN_CODE_START 0x9200

asm (
    "xorw %ax, %ax\n\t"
    "movw %ax, %ds\n\t"
    /*Linker places the start of the .data section at this address */
    "movw (0x9202), %ax\n\t"
    "movw %ax, %ds\n\t"
    "movw %ax, %ss\n\t"
    "movw $0xFFFB, %sp\n\t" 
    "jmp main"
);

void print_char(char c){
    asm volatile("int  $0x10" : : "a"(0x0E00 | c), "b"(7));
}

void get_int_addr(ushort interrupt, FullAddr *addr)
{
    asm volatile(
        "pushw %%ds\n\t"
        "movw %w3, %%ds\n\t"
        "movw (%w2), %w0\n\t"
        "movw 2(%w2), %w1\n\t"
        "popw %%ds"
        : "=c"(addr->offset), "=a"(addr->seg):"b"(interrupt*4),"a"(0)
    );
}

void set_int_addr(ushort interrupt, uint func){
    asm volatile(
        "cli\n\t"
        "pushw %%ds\n\t"
        "movw %w2, %%ds\n\t"
        "movw %w0, (%w1)\n\t"
        "movw %%cs, 2(%w1)\n\t"
        "popw %%ds\n\t"
        "sti"
        : : "c"(func-MAIN_CODE_START), "b"(interrupt*4), "a"(0):
    );
}

void wait(uint usec)
{
    asm volatile("int $0x15": : "a"(0x8600), "c"(usec>>16), "d"(usec&0xFFFF));
}

FullAddr addr;

void handler_func(){
    print_char('A');
}

void handler();
asm(
    "handler:\n\t"
    "pushal\n\t"
    "call handler_func\n\t"
    "popal\n\t"
    "ljmp *addr\n\t"
    "iret\n\t"
);

void main(){
    get_int_addr(9, &addr);
    set_int_addr(9, (uint)handler);
    while(1){
        wait(1000);
    }
}    

The full project can be downloaded here, it includes floppy image. To build it, launch files build.sh and build_main.sh.

Update: I've tried @RossRidge code — the lcall instruction jumps to 0xfe9e6 instead of 0xfe897 and there is an infinite loop. handler code:

asm(
    "handler:\n\t"
    "pushw %ds\n\t"
    "pushal\n\t"
    "xorw %ax, %ax\n\t"
    "movw %ax, %ds\n\t"
    "movw (0x9202), %ax\n\t"
    "movw %ax, %ds\n\t"
    "call handler_func\n\t"
    "popal\n\t"
    "pushf\n\t"
    "lcall *addr\n\t"
    "popw %ds\n\t"
    "iret\n\t"
);

Update2: I've figured out that I have to use lcallw instead, but the infinite loop remains. After iret execution goes to 0xfe9e6 and then back to iret.

Upvotes: 2

Views: 198

Answers (1)

Ross Ridge
Ross Ridge

Reputation: 39551

Your problem is likely the ljmp *addr statement. The memory location addr is relative to DS, but in an interrupt handler this can be anything. Since its almost certain that the handler will be called in the context of the BIOS code that handles int $0x15, %ah = 0x85, DS will be set to whatever the BIOS sets it to, not what your code sets it to.

The simple solution would be to make memory location relative to CS with ljmp *%cs:addr, but your code uses different values for CS and DS so that won't work. You should really fix that so they're the same, but failing that you'll have to use something like ljmp *cs:addr-0x9200.

Upvotes: 2

Related Questions