OSDev.org
https://forum.osdev.org/

COM1 IRQ Not Triggering Every Time
https://forum.osdev.org/viewtopic.php?f=1&t=57184
Page 1 of 1

Author:  mttarry [ Sun Mar 24, 2024 2:31 pm ]
Post subject:  COM1 IRQ Not Triggering Every Time

When I send serial data via QEMU's virtual serial port from my host PC to my guest OS, I am seeing that my IRQ4 for COM1 does not trigger every time. On average it will trigger about 50% of the time. I can't figure out how I can determine whether it is QEMU errata or an issue with how I've setup my PIC. The fact that it triggers some of the time at least tells me that I have my interrupts mapped correctly, and that the IRQ handler is in the right place.

I'm looking for any advice that might help me determine why IRQ4 is not triggering for each byte of serial data that is sent. I have not tested this on hardware yet because I don't have a serial cable at the moment. Thank you in advance for any help you can provide.

These are my QEMU flags:
Code:
QEMU_FLAGS = -cdrom $(ISO_TARGET) -display cocoa,full-screen=on -monitor stdio -serial pty


And I'm sending data from my host via:
Code:
echo -n h > /dev/ttys002

About 50% of the time I issue this command from my host, an IRQ is not triggered on my kernel.

Here is the relevant code:

irq.c
Code:
void irq_remap(void) {
    // ICW1
    outb(PIC1_CMD, ICW1_ICW4 | ICW1_INIT);
    outb(PIC2_CMD, ICW1_ICW4 | ICW1_INIT);

    // ICW2
    outb(PIC1_DATA, ICW2_PIC1_BASE);
    outb(PIC2_DATA, ICW2_PIC2_BASE);

    // ICW3
    outb(PIC1_DATA, ICW3_PIC1_IRQ); // master PIC at IRQ4
    outb(PIC2_DATA, ICW3_PIC2_IRQ); // slave PIC at IRQ2

    // ICW4
    outb(PIC1_DATA, ICW4_8086);
    outb(PIC2_DATA, ICW4_8086);

    // Unmask IRQ4 -- Todo: handle this in separate logic
    outb(PIC1_DATA, 0xEF);   
   outb(PIC2_DATA, 0xFF);
}

static uint16_t pic_get_irq_reg(int ocw3) {
    outb(PIC1_CMD, ocw3);
    outb(PIC2_CMD, ocw3);
    return (inb(PIC2_CMD) << 8) | inb(PIC1_CMD);
}

uint16_t pic_get_irr(void) {
    return pic_get_irq_reg(PIC_OCW3_IRR);
}

uint16_t pic_get_isr(void) {
    return pic_get_irq_reg(PIC_OCW3_ISR);
}

void pic_endof_int(uint8_t irq) {
   if (irq >= 8)
      outb(PIC2_CMD, PIC_EOI);

   outb(PIC1_CMD, PIC_EOI);
}

void irq_handler() {
    int irq = BIT_INDEX(pic_get_isr());
    kprintf(".");

    if (irq_handlers[irq] != NULL) {
        irq_handlers[irq]();
    }

    outb(PIC1_DATA, 0xEF);   
   outb(PIC2_DATA, 0xFF);

    pic_endof_int(irq);
}


void irq_set_gates() {
    for (int i = IRQ_VEC_START; i < IRQ_VEC_END; ++i) {
        idt_set_desc(i, &irq_stub, IDTENTRY_KERNEL_INT);
    }
}

void irq_install() {
    irq_remap();
    irq_set_gates();
}

void register_irq_handler(int irq_num, void* handler) {
    if (irq_num >= 0 && irq_num < MAX_IRQS) {
        irq_handlers[irq_num] = handler;
    }
}


serial.c
Code:
int serial_init(void) {
    outb(SERIAL_PORT + IER_OFFSET, 0x01); // enable interrupts

    outb(SERIAL_PORT + LCR_OFFSET, 0x80); // set DLAB
    outb(SERIAL_PORT + DIVISOR_LSB_OFFSET, 0x01); // set LSB of divisor
    outb(SERIAL_PORT + DIVISOR_MSB_OFFSET, 0x00); // set MSB of divisor
    outb(SERIAL_PORT + LCR_OFFSET, 0x00); // clear DLAB

    //outb(SERIAL_PORT + FCR_OFFSET, 0xC7); // enable FIFO, clear buffers, 14-byte threshold
    outb(SERIAL_PORT + FCR_OFFSET, 0x00); // disable FIFO

    outb(SERIAL_PORT + LCR_OFFSET, 0x03); // 8-bit, no parity, 1 stop bit
    outb(SERIAL_PORT + MCR_OFFSET, 0x1E); // loopback mode, test the serial chip
    outb(SERIAL_PORT, 0xAB);

    if (inb(SERIAL_PORT) != 0xAB) {
        return 1;
    }

    // serial is not faulty, set in normal operation mode
    outb(SERIAL_PORT + MCR_OFFSET, 0x0F); // interrupt enabled, RTS and DSR set
   
    register_irq_handler(SERIAL_PORT_IRQ, serial_irq_handler);

    return 0;
}

static void serial_handle_receive_byte() {
    uint8_t recb = inb(SERIAL_PORT);
    kprintf("%c", recb);
}

static void serial_irq_handler() {
    // Determine if we are reading/writing UART
    uint8_t lsr = inb(SERIAL_PORT + LSR_OFFSET);
    uint8_t iir = inb(SERIAL_PORT + IIR_OFFSET);

    // Check if data is ready to be extracted from the UART
    if (lsr & 1) {
        serial_handle_receive_byte();
    }
}

}


Attachments:
serial.c [2.44 KiB]
Downloaded 5 times
gdt.c [2.71 KiB]
Downloaded 6 times
idt.c [1.08 KiB]
Downloaded 5 times

Author:  Octocontrabass [ Sun Mar 24, 2024 9:49 pm ]
Post subject:  Re: COM1 IRQ Not Triggering Every Time

mttarry wrote:
Code:
    outb(SERIAL_PORT + DIVISOR_LSB_OFFSET, 0x01); // set LSB of divisor
    outb(SERIAL_PORT + DIVISOR_MSB_OFFSET, 0x00); // set MSB of divisor

Have you tried a bigger divisor? Maybe it's just too fast without the FIFO.

Author:  mttarry [ Mon Mar 25, 2024 4:10 pm ]
Post subject:  Re: COM1 IRQ Not Triggering Every Time

Octocontrabass wrote:
mttarry wrote:
Code:
    outb(SERIAL_PORT + DIVISOR_LSB_OFFSET, 0x01); // set LSB of divisor
    outb(SERIAL_PORT + DIVISOR_MSB_OFFSET, 0x00); // set MSB of divisor

Have you tried a bigger divisor? Maybe it's just too fast without the FIFO.


I just tried increasing the divisor ( tried values 0x01-0xFF), and still see the same behavior. I also repeated that with the FIFO being enabled. Out of 20 runs of
Code:
echo -n "hi" > /dev/ttys002
, the IRQ4 will trigger anywhere between 8-14 times. At this point I'm thinking that maybe it's something to do with the MacOS port of qemu-system-i386, but any other ideas would be greatly appreciated.

Author:  Octocontrabass [ Mon Mar 25, 2024 6:24 pm ]
Post subject:  Re: COM1 IRQ Not Triggering Every Time

The only other thing that caught my eye is that you're reading the interrupt controller's in-service register to check which interrupt you're handling. Have you tried passing the interrupt vector from your interrupt stub instead?

Author:  mttarry [ Mon Mar 25, 2024 6:44 pm ]
Post subject:  Re: COM1 IRQ Not Triggering Every Time

I've just identified that with QEMU -serial stdio, my COM1 IRQ fires 100% of the time and I can retrieve the serial data that way. For some reason the -serial pty option, which opens a virtual serial device file on my host, causes the interrupt not to fire all the time.

Page 1 of 1 All times are UTC - 6 hours
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
http://www.phpbb.com/