void hal_uart_init(void);
void hal_uart_drain_rx(void);
+void hal_uart_poll_rx(void);
void hal_uart_putc(char c);
int hal_uart_try_getc(void);
void hal_uart_set_rx_callback(void (*cb)(char));
#include "vga_console.h"
#include "hal/timer.h"
+#include "hal/uart.h"
static uint32_t tick = 0;
tick++;
vdso_update_tick(tick);
vga_flush();
+ hal_uart_poll_rx();
process_wake_check(tick);
schedule();
}
void hal_uart_drain_rx(void) {
}
+void hal_uart_poll_rx(void) {
+}
+
void hal_uart_putc(char c) {
volatile uint32_t* uart = (volatile uint32_t*)UART_BASE;
while (uart[6] & (1 << 5)) { }
(void)mmio_read8(UART_BASE);
}
+void hal_uart_poll_rx(void) {
+}
+
void hal_uart_putc(char c) {
while ((mmio_read8(UART_BASE + 5) & 0x20) == 0) { }
mmio_write8(UART_BASE, (uint8_t)c);
(void)mmio_read8(UART_BASE);
}
+void hal_uart_poll_rx(void) {
+}
+
void hal_uart_putc(char c) {
while ((mmio_read8(UART_BASE + 5) & 0x20) == 0) { }
mmio_write8(UART_BASE, (uint8_t)c);
outb(UART_BASE + 0, 0x03); /* Baud 38400 */
outb(UART_BASE + 1, 0x00);
outb(UART_BASE + 3, 0x03); /* 8N1 */
- outb(UART_BASE + 2, 0xC7); /* Enable FIFO */
+ outb(UART_BASE + 2, 0x07); /* Enable FIFO, clear both, 1-byte trigger */
outb(UART_BASE + 4, 0x0B); /* DTR + RTS + OUT2 */
/* Register IRQ 4 handler (IDT vector 36 = 32 + 4) */
}
void hal_uart_drain_rx(void) {
- /* Drain any pending characters from the UART FIFO.
- * This de-asserts the IRQ line so that the next character
- * produces a clean rising edge for the IOAPIC (edge-triggered). */
- (void)inb(UART_BASE + 2); /* Read IIR to ack any pending */
- while (inb(UART_BASE + 5) & 0x01) /* Drain RX FIFO */
+ /* Full UART interrupt reinitialisation for IOAPIC hand-off.
+ *
+ * hal_uart_init() runs under the legacy PIC and enables IER bit 0
+ * (RX interrupt). By the time the IOAPIC routes IRQ 4 as
+ * edge-triggered, the UART IRQ line may already be asserted —
+ * the IOAPIC will never see a rising edge and serial input is
+ * permanently dead.
+ *
+ * Fix: temporarily disable ALL UART interrupts so the IRQ line
+ * goes LOW, drain every pending condition, then re-enable IER.
+ * The next character will produce a clean LOW→HIGH edge. */
+
+ /* 1. Disable all UART interrupts — IRQ line goes LOW */
+ outb(UART_BASE + 1, 0x00);
+
+ /* 2. Drain the RX FIFO */
+ while (inb(UART_BASE + 5) & 0x01)
(void)inb(UART_BASE);
- (void)inb(UART_BASE + 6); /* Read MSR to clear delta bits */
+
+ /* 3. Read IIR until "no interrupt pending" (bit 0 set) */
+ for (int i = 0; i < 16; i++) {
+ uint8_t iir = inb(UART_BASE + 2);
+ if (iir & 0x01) break;
+ }
+
+ /* 4. Clear modem-status delta bits */
+ (void)inb(UART_BASE + 6);
+
+ /* 5. Clear line-status error bits */
+ (void)inb(UART_BASE + 5);
+
+ /* 6. Re-enable RX interrupt — next character will assert a clean edge */
+ outb(UART_BASE + 1, 0x01);
+}
+
+void hal_uart_poll_rx(void) {
+ /* Timer-driven fallback: drain any pending characters from the
+ * UART FIFO via polling. Called from the timer tick handler so
+ * serial input works even if the IOAPIC edge-triggered IRQ for
+ * COM1 is never delivered (observed in QEMU i440FX). */
+ while (inb(UART_BASE + 5) & 0x01) {
+ char c = (char)inb(UART_BASE);
+ if (uart_rx_cb) uart_rx_cb(c);
+ }
}
void hal_uart_set_rx_callback(void (*cb)(char)) {
--- /dev/null
+#!/usr/bin/expect -f
+#
+# AdrOS Serial Input Test
+# Verifies that typing on the serial console works with /bin/sh.
+#
+# Usage: expect tests/test_serial_input.exp [timeout_sec]
+# timeout_sec: max seconds (default: 30)
+#
+# Exit codes:
+# 0 = serial input works (echo received)
+# 1 = serial input broken (no echo / no response)
+# 2 = timeout (boot did not complete)
+
+set timeout_sec [lindex $argv 0]
+if {$timeout_sec eq ""} { set timeout_sec 30 }
+
+set iso "adros-x86.iso"
+set disk "disk.img"
+
+# Ensure disk image exists
+if {![file exists $disk]} {
+ exec dd if=/dev/zero of=$disk bs=1M count=4 2>/dev/null
+}
+
+# Create a temporary grub.cfg that boots directly into shell+serial
+set tmpdir [exec mktemp -d]
+file mkdir "$tmpdir/boot/grub"
+set fd [open "$tmpdir/boot/grub/grub.cfg" w]
+puts $fd {set timeout=0
+set default=0
+menuentry "AdrOS serial shell" {
+ multiboot2 /boot/adros-x86.bin init=/bin/sh console=serial
+ module2 /boot/initrd.img
+ boot
+}}
+close $fd
+
+# Build a temporary ISO with the modified grub.cfg
+set tmp_iso "$tmpdir/adros-serial-test.iso"
+# Copy original ISO contents
+file mkdir "$tmpdir/iso"
+exec cp -r iso/boot "$tmpdir/iso/"
+file copy -force "$tmpdir/boot/grub/grub.cfg" "$tmpdir/iso/boot/grub/grub.cfg"
+exec grub-mkrescue -o $tmp_iso "$tmpdir/iso" 2>/dev/null
+
+set timeout $timeout_sec
+
+# Spawn QEMU with serial on stdio so expect can interact
+spawn qemu-system-i386 \
+ -smp 4 -boot d -cdrom $tmp_iso -m 128M -display none \
+ -drive file=$disk,if=ide,format=raw \
+ -nic user,model=e1000 \
+ -serial mon:stdio -monitor none \
+ -no-reboot -no-shutdown
+
+set qemu_id $spawn_id
+
+# Wait for shell prompt ($ )
+expect {
+ -re {\$ } {
+ # Shell prompt appeared — good
+ }
+ timeout {
+ puts "\n\[SERIAL-INPUT\] FAIL: Timeout waiting for shell prompt"
+ catch {exec kill [exp_pid]}
+ file delete -force $tmpdir
+ exit 2
+ }
+ eof {
+ puts "\n\[SERIAL-INPUT\] FAIL: QEMU exited before shell prompt"
+ file delete -force $tmpdir
+ exit 1
+ }
+}
+
+# Send a test command
+send "echo SERIAL_INPUT_OK\r"
+
+# Wait for the output
+expect {
+ "SERIAL_INPUT_OK" {
+ puts "\n\[SERIAL-INPUT\] PASS: Serial input works"
+ send "exit\r"
+ after 500
+ catch {exec kill [exp_pid]}
+ file delete -force $tmpdir
+ exit 0
+ }
+ timeout {
+ puts "\n\[SERIAL-INPUT\] FAIL: No response to typed command"
+ catch {exec kill [exp_pid]}
+ file delete -force $tmpdir
+ exit 1
+ }
+ eof {
+ puts "\n\[SERIAL-INPUT\] FAIL: QEMU exited unexpectedly"
+ file delete -force $tmpdir
+ exit 1
+ }
+}