OSDev.org

The Place to Start for Operating System Developers
It is currently Sat Apr 27, 2024 5:45 am

All times are UTC - 6 hours




Post new topic Reply to topic  [ 5 posts ] 
Author Message
 Post subject: COM1 IRQ Not Triggering Every Time
PostPosted: Sun Mar 24, 2024 2:31 pm 
Offline

Joined: Thu May 12, 2022 6:04 pm
Posts: 6
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 4 times
gdt.c [2.71 KiB]
Downloaded 5 times
idt.c [1.08 KiB]
Downloaded 4 times
Top
 Profile  
 
 Post subject: Re: COM1 IRQ Not Triggering Every Time
PostPosted: Sun Mar 24, 2024 9:49 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
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.


Top
 Profile  
 
 Post subject: Re: COM1 IRQ Not Triggering Every Time
PostPosted: Mon Mar 25, 2024 4:10 pm 
Offline

Joined: Thu May 12, 2022 6:04 pm
Posts: 6
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.


Top
 Profile  
 
 Post subject: Re: COM1 IRQ Not Triggering Every Time
PostPosted: Mon Mar 25, 2024 6:24 pm 
Offline
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5146
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?


Top
 Profile  
 
 Post subject: Re: COM1 IRQ Not Triggering Every Time
PostPosted: Mon Mar 25, 2024 6:44 pm 
Offline

Joined: Thu May 12, 2022 6:04 pm
Posts: 6
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.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: SemrushBot [Bot] and 31 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group