Search code examples
assemblyx86operating-systeminterrupt

Why programmable interval timer cannot work while other device work?


My OS can properly handle keyboard and mouse interrupt, but PIT cannot work at all.

void int_handler_32() {
    PIC_send_EOI(0);
    term_printf("-");
    kPrintf("!");
}

void timer_init() {
    kPrintf("!");
    cli();
    uint16_t hz = TIMER_IN_HZ / TIMER_TARGET_HZ;
    outb(0x43, 0x34);
    outb(0x40, hz & 0xFF);
    outb(0x40, hz >> 8);
    // sti();
}

/**************************/

void idt_init() {
    pic_init();
    // PIC_remap(0x20, 0x28);

    idtr.base = (uint32_t)idt;
    idtr.limit = sizeof(idt_entry_t) * 256 - 1;

    for (uint16_t i = 0; i < 256; i++) {
        idt_set_descriptor(i, 0, 0, 0);
    }

#define ADD_INT(n) \
    void isr_wrapper_##n(); \
    idt_set_descriptor(n, 0x08, isr_wrapper_##n, 0x8E)

    ADD_INT(32); // timer
    ADD_INT(33); // keyboard
    ADD_INT(44); // mouse
    ADD_INT(128);// custom

    asm volatile ("lidt %0":: "m" (idtr));

    asm volatile ("sti");
    IRQ_set_mask(0x20);
    IRQ_set_mask(0x21);
    IRQ_set_mask(0x2C);
}

/**************************/

static Queue qKeyboard;
static Queue qMouse;

void int_handler_33() {
    PIC_send_EOI(1);
    uint8_t ch = inb(0x60);
    queue_push(&qKeyboard, &ch);
    // kPrintf("+K %d\n", ch);
}

void int_handler_44() {
    PIC_send_EOI(12);
    uint8_t ch = inb(0x60);
    queue_push(&qMouse, &ch);
    // kPrintf("+M %xb\n", ch);
}
# codes generate a .s file
for i in 32 33 44 128; do
    cat << EOF
.global isr_wrapper_$i
.type isr_wrapper_$i, @function
isr_wrapper_$i:
    pusha
    cld
    call int_handler_$i
    popa
    iret
EOF
done

I've just have no idea now. By outputting to qemu's port 0x3F8, I've confirmed that isr_wrapper 33, 44 are called but 32 aren't.

Can anyone gives some suggestions? I've read the I Can't Get Interrupts Working article on osdev, but cannot find any clue.


After debugging with bochs, the logs shows that IRQ0 is changed but it doesn't signal me.

01399359954d[PIC   ] IRQ line 0 now high
01399399946d[PIC   ] IRQ line 0 now low
01399399950d[PIC   ] IRQ line 0 now high
01399421000d[PIC   ] IRQ line 1 now high
01399421000d[PIC   ] signalling IRQ #1
01399421032d[PIC   ] IO write to 0x0020 = 0x20
01399421051d[PIC   ] IRQ line 1 now low
01399439946d[PIC   ] IRQ line 0 now low
01399439950d[PIC   ] IRQ line 0 now high
01399479942d[PIC   ] IRQ line 0 now low
01399479946d[PIC   ] IRQ line 0 now high

Comparing to line 1(keyboard), it should signal IRQ #0 but it doesn't. I've searched this question which is quite similar to my problem. His problem is that he doesn't set IF bit with sti, but in my case this couldn't happen as keyboard works.


Solution

  • After carefully checking my code, I've find the reason!

    I used to set IMR before the main loop like below (for keyboard and mouse message handling) and have forgotten that (I think I set this mask right after idt_init).

    void input_start() {
        outb(0x21, 0xF9);
        outb(0xA1, 0xEF);
    }
    

    I hard-coded the mask which set the bit zero which make PIC ignore the timer. After remove this function, timer works.