mirror of
https://github.com/Plexi09/Nadir-Kernel.git
synced 2026-10-20 15:07:27 +02:00
Merge pull request #4 from Plexi09/feature/full-interrupts-time-input
io.h (inb/outb/io_wait), 8259 PIC remap IRQ 0–15 → vectors 32–47, pic_eoi/mask/unmask, 16 IRQ gates + irq_dispatch/irq_register_handler in isr.asm/idt.c. Exceptions 0–31 untouched. 8254 PIT ch0 mode 3 @100hz on IRQ0, volatile tick counter, pit_sleep hlt-loop. COM1 38400 8N1 polling log (serial_ok open-bus check), PS/2 set-1 IRQ1 driver (128-entry ring, Shift/Caps XOR, E0/release handling, \b/\n passthrough). init order idt_init → pic_remap → idt_install_irqs → serial → pit → keyboard → unmask(0,1) → sti, then hlt loop with uptime S.Ts ~1/s + keyboard echo (VGA + serial mirror). \b erases via console_putchar.
This commit is contained in:
commit
3868884e3c
15 changed files with 1003 additions and 11 deletions
18
Makefile
18
Makefile
|
|
@ -20,7 +20,7 @@ CFLAGS := -std=c17 -ffreestanding -nostdlib -fno-builtin \
|
|||
-fno-pic -fno-pie -fno-stack-protector \
|
||||
-Wall -Wextra -Werror -O2 -Iinclude
|
||||
|
||||
OBJS := $(BUILD)/entry.o $(BUILD)/isr.o $(BUILD)/kmain.o $(BUILD)/console.o $(BUILD)/idt.o
|
||||
OBJS := $(BUILD)/entry.o $(BUILD)/isr.o $(BUILD)/kmain.o $(BUILD)/console.o $(BUILD)/idt.o $(BUILD)/pic.o $(BUILD)/pit.o $(BUILD)/serial.o $(BUILD)/keyboard.o
|
||||
|
||||
all: $(IMG)
|
||||
|
||||
|
|
@ -33,13 +33,25 @@ $(BUILD)/entry.o: $(ARCH_DIR)/entry.asm | $(BUILD)
|
|||
$(BUILD)/isr.o: $(ARCH_DIR)/isr.asm | $(BUILD)
|
||||
$(NASM) -f elf64 $< -o $@
|
||||
|
||||
$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h include/idt.h | $(BUILD)
|
||||
$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h include/idt.h include/pic.h include/pit.h include/serial.h include/keyboard.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/console.o: $(ARCH_DIR)/console.c include/console.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/idt.o: $(ARCH_DIR)/idt.c include/idt.h include/console.h | $(BUILD)
|
||||
$(BUILD)/idt.o: $(ARCH_DIR)/idt.c include/idt.h include/console.h include/pic.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/pic.o: $(ARCH_DIR)/pic.c include/pic.h include/io.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/pit.o: $(ARCH_DIR)/pit.c include/pit.h include/idt.h include/io.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/serial.o: $(ARCH_DIR)/serial.c include/serial.h include/io.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/keyboard.o: $(ARCH_DIR)/keyboard.c include/keyboard.h include/idt.h include/pic.h include/io.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/kernel.elf: $(OBJS) $(ARCH_DIR)/link.ld
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@
|
|||
* Privileged primitive: `lidt` in idt_init(). Fault behavior: before
|
||||
* idt_init() any exception triple-faults (no IDT); after, CPU vectors
|
||||
* 0-31 land in the assembly stubs (isr.asm) and then in
|
||||
* exception_handler(), which prints and halts. Vectors 32-255 are left
|
||||
* zero (not present) on purpose because PIC remap + IRQ handling is a later step.
|
||||
* exception_handler(), which prints and halts. Vectors 32-47 stay
|
||||
* not-present until the 8259 PIC is remapped and idt_install_irqs()
|
||||
* installs the IRQ gates; then they land in irq_dispatch(), which
|
||||
* sends the PIC EOI and calls the per-IRQ handler (if any).
|
||||
* Interrupts stay disabled throughout (IF=0); exceptions still fire.
|
||||
* IRQs only fire after the integrator enables them with `sti`.
|
||||
*/
|
||||
|
||||
#ifndef NADIR_IDT_H
|
||||
|
|
@ -17,6 +20,10 @@
|
|||
/* Number of CPU exception vectors this base installs (0-31). */
|
||||
#define IDT_EXCEPTION_COUNT 32
|
||||
|
||||
/* First remapped IRQ vector (IRQ 0 -> vector 32) and IRQ line count. */
|
||||
#define IDT_IRQ_BASE 32
|
||||
#define IDT_IRQ_COUNT 16
|
||||
|
||||
/* Stack layout on entry to exception_handler().
|
||||
*
|
||||
* Built by the CPU (RIP/CS/RFLAGS, plus an error code on vectors
|
||||
|
|
@ -63,4 +70,22 @@ void idt_init(void);
|
|||
* frame above. Prints vector/error/RIP and halts; never returns. */
|
||||
void exception_handler(struct interrupt_frame *frame);
|
||||
|
||||
/* C entry point for vectors 32-47, called from irq_common with the
|
||||
* same frame layout (vector = 32 + irq, error = 0 dummy). Runs at
|
||||
* Ring 0 with IF cleared; sends the PIC EOI and calls the handler
|
||||
* registered for that IRQ, if any. Spurious vectors outside 32-47
|
||||
* are ignored. */
|
||||
void irq_dispatch(struct interrupt_frame *frame);
|
||||
|
||||
/* Register `handler` for IRQ line `irq` (0-15); NULL unregisters.
|
||||
* Preconditions: Ring 0, `irq` < 16 (out-of-range calls ignored).
|
||||
* Failure modes: none; takes effect on the next IRQ after return. */
|
||||
void irq_register_handler(uint8_t irq, void (*handler)(void));
|
||||
|
||||
/* Install the 16 IRQ gates (vectors 32-47 -> irq stubs) with selector
|
||||
* 0x18 and flags 0x8E (present, DPL 0, 64-bit interrupt gate).
|
||||
* Preconditions: Ring 0, idt_init() already ran, PIC already remapped
|
||||
* via pic_remap(). Leaves IF cleared. Failure modes: none. */
|
||||
void idt_install_irqs(void);
|
||||
|
||||
#endif /* NADIR_IDT_H */
|
||||
|
|
|
|||
47
include/io.h
Normal file
47
include/io.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/* Nadir x86 port I/O primitives (inb/outb/io_wait).
|
||||
*
|
||||
* Runs in 64-bit long mode at Ring 0 (CPL 0) with the stage-2 GDT.
|
||||
* Privileged instructions: `in`/`out` trap with #GP(0) when CPL > IOPL,
|
||||
* so these helpers are Ring 0 only and must never be exposed to
|
||||
* userspace. Fault behavior: a bad port number cannot fault by itself
|
||||
* (unused ports read back 0xFF or are ignored); a fault here means the
|
||||
* caller programmed the wrong device, not a CPU trap.
|
||||
* Interrupts stay disabled by the caller where timing matters; these
|
||||
* helpers do not touch IF themselves.
|
||||
*/
|
||||
|
||||
#ifndef NADIR_IO_H
|
||||
#define NADIR_IO_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Read one byte from ISA port `port`.
|
||||
* Preconditions: CPL 0, port is a valid x86 I/O address (0-0xFFFF).
|
||||
* Failure modes: none (returns bus value; 0xFF on unmapped ports). */
|
||||
static inline uint8_t inb(uint16_t port)
|
||||
{
|
||||
uint8_t value;
|
||||
|
||||
__asm__ volatile("inb %1, %0" : "=a"(value) : "Nd"(port));
|
||||
return value;
|
||||
}
|
||||
|
||||
/* Write byte `value` to ISA port `port`.
|
||||
* Preconditions: CPL 0, port is writable for this device.
|
||||
* Failure modes: none from the CPU; wrong port = device misbehavior. */
|
||||
static inline void outb(uint16_t port, uint8_t value)
|
||||
{
|
||||
__asm__ volatile("outb %0, %1" ::"a"(value), "Nd"(port));
|
||||
}
|
||||
|
||||
/* Tiny delay for legacy ISA devices (writes to unused port 0x80).
|
||||
*
|
||||
* WHY: the 8259 PIC and other slow devices need ~1us between back to
|
||||
* back command bytes; port 0x80 is the conventional throwaway delay.
|
||||
* Preconditions: CPL 0. Failure modes: none. */
|
||||
static inline void io_wait(void)
|
||||
{
|
||||
outb(0x80U, 0U);
|
||||
}
|
||||
|
||||
#endif /* NADIR_IO_H */
|
||||
42
include/keyboard.h
Normal file
42
include/keyboard.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/* Nadir PS/2 keyboard: scancode set-1 IRQ1 driver with ASCII queue.
|
||||
*
|
||||
* Runs in 64-bit long mode at Ring 0 (CPL 0); uses `inb` on ports
|
||||
* 0x60 (data) / 0x64 (status), which fault with #GP above CPL 0, so
|
||||
* this driver is kernel-only. QEMU/SeaBIOS provides a PS/2 controller
|
||||
* in set-1 mode; the 8259 routes IRQ1 to vector 33 after the PIC remap.
|
||||
* Fault behavior: port I/O cannot fault at CPL 0. Reading 0x60 when
|
||||
* the controller has no byte returns stale data, so keyboard_on_irq()
|
||||
* must run only from IRQ1 (data-ready guaranteed).
|
||||
*
|
||||
* Locking: single core, one producer (IRQ1 handler) and one consumer
|
||||
* (polling kmain). `volatile` head/tail plus single-byte loads/stores
|
||||
* suffice — no locks, no atomics. IF stays cleared; the queue is both
|
||||
* polling-safe and IRQ-safe under that model.
|
||||
*
|
||||
* Preconditions: call keyboard_init() once before use. It registers
|
||||
* keyboard_on_irq() for IRQ1 (via irq_register_handler) and unmasks
|
||||
* the line (via pic_unmask); the PIC remap, IRQ-gate install, and
|
||||
* `sti` stay integrator duty, and IF stays cleared until then.
|
||||
* Failure modes: unknown/extended/release codes are dropped silently;
|
||||
* a full queue drops the newest byte; dequeue on empty returns 0.
|
||||
*/
|
||||
|
||||
#ifndef NADIR_KEYBOARD_H
|
||||
#define NADIR_KEYBOARD_H
|
||||
|
||||
/* Clear the queue and modifier state, register keyboard_on_irq() for
|
||||
* IRQ1, and unmask IRQ1. */
|
||||
void keyboard_init(void);
|
||||
|
||||
/* IRQ1 entry point: read one scancode from 0x60, translate, enqueue.
|
||||
* Runs in interrupt context. */
|
||||
void keyboard_on_irq(void);
|
||||
|
||||
/* Nonzero if at least one translated byte waits in the queue. */
|
||||
int keyboard_has_key(void);
|
||||
|
||||
/* Remove and return the oldest byte, or 0 when the queue is empty.
|
||||
* Backspace arrives as '\b', Enter as '\n'. */
|
||||
char keyboard_dequeue(void);
|
||||
|
||||
#endif /* NADIR_KEYBOARD_H */
|
||||
58
include/pic.h
Normal file
58
include/pic.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/* Nadir 8259 PIC driver: remap, EOI, IRQ masking.
|
||||
*
|
||||
* Runs in 64-bit long mode at Ring 0 (CPL 0) with the stage-2 GDT.
|
||||
* Privileged instructions: `outb`/`inb` (via io.h) trap with #GP(0)
|
||||
* when CPL > IOPL, so every entry point below is Ring 0 only.
|
||||
* Fault behavior: no CPU fault is possible beyond a programming bug;
|
||||
* a wrong sequence masks the wrong IRQ or loses an EOI (hung line),
|
||||
* never a triple fault by itself. IF stays cleared by the caller;
|
||||
* this driver never executes `sti`/`cli`.
|
||||
*/
|
||||
|
||||
#ifndef NADIR_PIC_H
|
||||
#define NADIR_PIC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Command/data ports for the master (PIC1) and slave (PIC2). */
|
||||
#define PIC1_CMD 0x20U
|
||||
#define PIC1_DATA 0x21U
|
||||
#define PIC2_CMD 0xA0U
|
||||
#define PIC2_DATA 0xA1U
|
||||
|
||||
/* End-of-interrupt command byte. */
|
||||
#define PIC_EOI 0x20U
|
||||
|
||||
/* Remapped vector bases: IRQ 0-7 -> vectors 32-39, IRQ 8-15 -> 40-47. */
|
||||
#define PIC_IRQ0_VECTOR 0x20U
|
||||
#define PIC_IRQ8_VECTOR 0x28U
|
||||
|
||||
/* Number of IRQ lines behind the cascaded 8259 pair. */
|
||||
#define PIC_IRQ_COUNT 16U
|
||||
|
||||
/* Remap the cascaded 8259 pair to vectors 32-47 and mask everything.
|
||||
* After reset IRQs overlap CPU exceptions 0-15, so without a
|
||||
* remap a timer tick looks like a fault. Mask-all leaves each device
|
||||
* opt-in via pic_unmask().
|
||||
* Preconditions: Ring 0, called once before idt_install_irqs().
|
||||
* Failure modes: none. */
|
||||
void pic_remap(void);
|
||||
|
||||
/* Signal end-of-interrupt for IRQ line `irq` (0-15).
|
||||
* WHY cascade order: the slave must be acked before the master or the
|
||||
* master keeps the cascade line busy and IRQ 2 stops firing.
|
||||
* Preconditions: Ring 0, `irq` < 16.
|
||||
* Failure modes: out-of-range `irq` is ignored. */
|
||||
void pic_eoi(uint8_t irq);
|
||||
|
||||
/* Mask (disable) IRQ line `irq` (0-15).
|
||||
* Preconditions: Ring 0, `irq` < 16.
|
||||
* Failure modes: out-of-range `irq` is ignored. */
|
||||
void pic_mask(uint8_t irq);
|
||||
|
||||
/* Unmask (enable) IRQ line `irq` (0-15).
|
||||
* Preconditions: Ring 0, `irq` < 16, PIC already remapped.
|
||||
* Failure modes: out-of-range `irq` is ignored. */
|
||||
void pic_unmask(uint8_t irq);
|
||||
|
||||
#endif /* NADIR_PIC_H */
|
||||
56
include/pit.h
Normal file
56
include/pit.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/* Nadir 8254 PIT timer driver (see pit.c).
|
||||
*
|
||||
* Runs in 64-bit long mode at Ring 0 (CPL 0): programs the PIT via `outb`
|
||||
* to ISA ports PIT_CMD (0x43) and PIT_CH0 (0x40), and handles IRQ0
|
||||
* (8259 PIC IRQ0 -> CPU vector 32 after pic_remap).
|
||||
*
|
||||
* Ports used: 0x43 (command/mode register, write-only), 0x40 (channel 0
|
||||
* data, write-only for the divisor latch).
|
||||
*
|
||||
* Preconditions: caller owns interrupt policy — pit_init() neither enables
|
||||
* (sti) nor disables (cli) interrupts; target init order is
|
||||
* pic_remap -> idt_install_irqs -> pit_init -> pic_unmask(0) -> sti.
|
||||
* pit_sleep() requires interrupts enabled, otherwise `hlt` never wakes.
|
||||
*
|
||||
* Failure modes: divisor is fixed at compile time (PIT_DIVISOR); a zero
|
||||
* divisor would mean "65536" to the hardware and the wrong rate, so it is
|
||||
* rejected with a compile-time _Static_assert. Frequency is fixed at 100Hz;
|
||||
* any other rate needs a deliberate driver change, not a runtime argument.
|
||||
*/
|
||||
|
||||
#ifndef NADIR_PIT_H
|
||||
#define NADIR_PIT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* PIT input clock: crystal-derived base rate shared by all IBM-PC PITs. */
|
||||
#define PIT_BASE_HZ 1193182U
|
||||
/* Driver tick rate: 100Hz = 10ms per tick. */
|
||||
#define PIT_HZ 100U
|
||||
/* Divisor latched into channel 0 (1193182/100 = 11931). */
|
||||
#define PIT_DIVISOR (PIT_BASE_HZ / PIT_HZ)
|
||||
/* 8259 PIC IRQ line carrying channel-0 ticks (remapped to vector 32). */
|
||||
#define PIT_IRQ 0U
|
||||
|
||||
/* Program channel 0 for mode 3 square-wave at PIT_HZ and register
|
||||
* pit_on_tick as the IRQ0 handler (if irq_register_handler is linked in).
|
||||
* Ring 0 only. Idempotent: second and later calls do nothing. Leaves IF
|
||||
* exactly as found; the caller decides when to sti. */
|
||||
void pit_init(void);
|
||||
|
||||
/* IRQ0 handler: increments the tick counter. Called from IRQ context
|
||||
* (interrupts already gated by the CPU/IRQ stub); must stay async-safe
|
||||
* and never block. */
|
||||
void pit_on_tick(void);
|
||||
|
||||
/* Monotonic tick count since pit_init (10ms per tick at 100Hz). Wraps only
|
||||
* after ~5.8 billion years; unsigned subtraction stays correct across it. */
|
||||
uint64_t pit_ticks(void);
|
||||
|
||||
/* Busy-wait `ticks` ticks using `hlt` to save power between interrupts.
|
||||
* Requires interrupts enabled (IF=1) and the PIT IRQ unmasked, otherwise
|
||||
* the CPU sleeps forever. Uses unsigned delta so a counter wrap mid-sleep
|
||||
* still terminates. */
|
||||
void pit_sleep(uint64_t ticks);
|
||||
|
||||
#endif /* NADIR_PIT_H */
|
||||
35
include/serial.h
Normal file
35
include/serial.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/* Nadir serial log: COM1 (16550 UART) polling driver.
|
||||
*
|
||||
* Runs in 64-bit long mode at Ring 0 (CPL 0); uses `inb`/`outb` which fault
|
||||
* with #GP if executed above CPL 0, so this driver is kernel-only.
|
||||
* Ports: COM1 0x3F8-0x3FF (data, int-enable, divisor latch, FIFO, line
|
||||
* control, modem control, line status). QEMU/SeaBIOS provides COM1; the
|
||||
* VGA console stays primary and serial is the secondary log.
|
||||
* Fault behavior: port I/O cannot fault at CPL 0. The TX poll spins on
|
||||
* LSR bit 5 and hangs only if the UART hardware is absent or wedged —
|
||||
* call serial_ok() first to check LSR sanity.
|
||||
*
|
||||
* Preconditions: call serial_init() once before any put. serial_puts()
|
||||
* requires a valid NUL-terminated string (NULL is ignored, not crashed
|
||||
* on). Failure modes: on absent hardware, init writes go nowhere and
|
||||
* serial_ok() returns 0; TX polls may then spin forever.
|
||||
*/
|
||||
|
||||
#ifndef NADIR_SERIAL_H
|
||||
#define NADIR_SERIAL_H
|
||||
|
||||
/* Program COM1 for 38400 8N1 polling (interrupts off, FIFO on).
|
||||
* Ring 0 only. Safe to call again to re-program the port. */
|
||||
void serial_init(void);
|
||||
|
||||
/* Block until the THR is empty (LSR bit 5), then emit one byte. */
|
||||
void serial_putchar(char c);
|
||||
|
||||
/* Emit a NUL-terminated string via serial_putchar(). NULL is a no-op. */
|
||||
void serial_puts(const char *s);
|
||||
|
||||
/* Nonzero if a UART looks present (LSR reads back sane, not 0xFF
|
||||
* as an open bus returns). Zero means "do not trust TX polls". */
|
||||
int serial_ok(void);
|
||||
|
||||
#endif /* NADIR_SERIAL_H */
|
||||
|
|
@ -46,6 +46,21 @@ void console_putchar(char c)
|
|||
if (c == '\n') {
|
||||
cursor_row++;
|
||||
cursor_col = 0;
|
||||
} else if (c == '\b') {
|
||||
/* kmain echoes keyboard '\b' for editing. Without this the
|
||||
* raw 0x08 glyph would print instead of erasing. Move back one
|
||||
* cell and blank it (IRQ-safe: mainline only, IF=1 but the
|
||||
* IRQ handlers never touch VGA). */
|
||||
if (cursor_col > 0) {
|
||||
cursor_col--;
|
||||
} else if (cursor_row > 0) {
|
||||
cursor_row--;
|
||||
cursor_col = VGA_WIDTH - 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
vga[cursor_row * VGA_WIDTH + cursor_col] =
|
||||
(uint16_t)((COLOR_DEFAULT << 8) | ' ');
|
||||
} else {
|
||||
vga[cursor_row * VGA_WIDTH + cursor_col] =
|
||||
(uint16_t)((COLOR_DEFAULT << 8) | c);
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@
|
|||
* (kernel code selector 0x18). Privileged primitive: `lidt` in
|
||||
* idt_init(). Fault behavior: before idt_init() any fault
|
||||
* triple-faults; after, vectors 0-31 print diagnostics and halt.
|
||||
* Vectors 32-255 stay not-present until the PIC is remapped.
|
||||
* IF stays cleared; exceptions fire regardless of IF.
|
||||
* Vectors 32-47 stay not-present until pic_remap() +
|
||||
* idt_install_irqs() install the IRQ gates; vectors 48-255 stay
|
||||
* not-present (no APIC yet). IF stays cleared; exceptions fire
|
||||
* regardless of IF, IRQs only after the integrator runs `sti`.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "console.h"
|
||||
#include "idt.h"
|
||||
#include "pic.h"
|
||||
|
||||
/* 64-bit interrupt gate descriptor (Intel SDM Vol. 3, IDT gate). */
|
||||
struct idt_entry {
|
||||
|
|
@ -42,6 +45,14 @@ static struct idtr idtr;
|
|||
/* Stub addresses from isr.asm, one per exception vector 0-31. */
|
||||
extern uint64_t isr_stub_table[IDT_EXCEPTION_COUNT];
|
||||
|
||||
/* Stub addresses from isr.asm, one per remapped IRQ vector 32-47. */
|
||||
extern uint64_t irq_stub_table[IDT_IRQ_COUNT];
|
||||
|
||||
/* Per-IRQ handlers for lines 0-15; NULL means "EOI only".
|
||||
* WHY a plain table: one subsystem per file — device drivers own
|
||||
* their logic and only register a callback here. BSS-zeroed. */
|
||||
static void (*irq_handlers[IDT_IRQ_COUNT])(void);
|
||||
|
||||
/* Install one gate: handler address + segment + IST index + flags. */
|
||||
static void idt_set_gate(uint8_t vector, uint64_t handler, uint16_t selector,
|
||||
uint8_t ist, uint8_t flags)
|
||||
|
|
@ -86,6 +97,41 @@ void idt_init(void)
|
|||
__asm__ volatile("lidt %0" ::"m"(idtr) : "memory");
|
||||
}
|
||||
|
||||
void idt_install_irqs(void)
|
||||
{
|
||||
for (uint16_t i = 0; i < IDT_IRQ_COUNT; i++) {
|
||||
idt_set_gate((uint8_t)(IDT_IRQ_BASE + i), irq_stub_table[i],
|
||||
KERNEL_CS, 0, IDT_FLAG_PRESENT_INT);
|
||||
}
|
||||
}
|
||||
|
||||
void irq_register_handler(uint8_t irq, void (*handler)(void))
|
||||
{
|
||||
if (irq >= IDT_IRQ_COUNT) {
|
||||
return;
|
||||
}
|
||||
irq_handlers[irq] = handler;
|
||||
}
|
||||
|
||||
void irq_dispatch(struct interrupt_frame *frame)
|
||||
{
|
||||
uint64_t vector = frame->vector;
|
||||
uint8_t irq;
|
||||
|
||||
if (vector < IDT_IRQ_BASE || vector >= IDT_IRQ_BASE + IDT_IRQ_COUNT) {
|
||||
return;
|
||||
}
|
||||
irq = (uint8_t)(vector - IDT_IRQ_BASE);
|
||||
|
||||
/* WHY handler before EOI: the EOI re-arms the (edge-triggered) PIC
|
||||
* line, so acking first would let the same IRQ re-enter before the
|
||||
* driver finished. A NULL slot just gets an EOI. */
|
||||
if (irq_handlers[irq] != 0) {
|
||||
irq_handlers[irq]();
|
||||
}
|
||||
pic_eoi(irq);
|
||||
}
|
||||
|
||||
void exception_handler(struct interrupt_frame *frame)
|
||||
{
|
||||
uint64_t vector = frame->vector;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ bits 64
|
|||
section .text
|
||||
|
||||
global isr_stub_table
|
||||
global irq_stub_table
|
||||
extern exception_handler
|
||||
extern irq_dispatch
|
||||
|
||||
; Vectors without a CPU error code: normalize with a dummy 0.
|
||||
%macro ISR_NOERR 1
|
||||
|
|
@ -105,3 +107,91 @@ isr_stub_table:
|
|||
dq isr_stub_20, isr_stub_21, isr_stub_22, isr_stub_23
|
||||
dq isr_stub_24, isr_stub_25, isr_stub_26, isr_stub_27
|
||||
dq isr_stub_28, isr_stub_29, isr_stub_30, isr_stub_31
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; Nadir IRQ stubs: vectors 32-47 (remapped 8259 IRQs 0-15) -> irq_dispatch.
|
||||
;
|
||||
; Same Ring 0 / CPL 0 entry context as the exception stubs above: the CPU
|
||||
; entered with IF cleared (interrupt gate), no privilege change, so only
|
||||
; RIP/CS/RFLAGS were pushed. IRQs never carry a CPU error code, so every
|
||||
; stub normalizes with a dummy 0 exactly like ISR_NOERR, keeping the
|
||||
; [vector, error] layout — and thus struct interrupt_frame — identical to
|
||||
; the exception path. Field order must stay in sync with include/idt.h.
|
||||
; The EOI is sent by the C dispatcher (pic_eoi), not here, so a future
|
||||
; per-IRQ EOI policy only touches C. -mno-red-zone keeps pushes safe.
|
||||
|
||||
section .text
|
||||
%macro IRQ_NOERR 1
|
||||
global isr_stub_%1
|
||||
isr_stub_%1:
|
||||
push 0
|
||||
push %1
|
||||
jmp irq_common
|
||||
%endmacro
|
||||
|
||||
IRQ_NOERR 32
|
||||
IRQ_NOERR 33
|
||||
IRQ_NOERR 34
|
||||
IRQ_NOERR 35
|
||||
IRQ_NOERR 36
|
||||
IRQ_NOERR 37
|
||||
IRQ_NOERR 38
|
||||
IRQ_NOERR 39
|
||||
IRQ_NOERR 40
|
||||
IRQ_NOERR 41
|
||||
IRQ_NOERR 42
|
||||
IRQ_NOERR 43
|
||||
IRQ_NOERR 44
|
||||
IRQ_NOERR 45
|
||||
IRQ_NOERR 46
|
||||
IRQ_NOERR 47
|
||||
|
||||
; Common IRQ entry: save GPRs, call void irq_dispatch(frame in RDI),
|
||||
; restore GPRs, drop [vector, error], return to the interrupted code.
|
||||
; Unlike isr_common (which halts), this path must iretq: IRQs are
|
||||
; resumable. Interrupts stay disabled throughout (IF=0 on entry via an
|
||||
; interrupt gate; irq_dispatch runs with IF cleared; iretq restores the
|
||||
; caller's RFLAGS).
|
||||
irq_common:
|
||||
push rax
|
||||
push rbx
|
||||
push rcx
|
||||
push rdx
|
||||
push rsi
|
||||
push rdi
|
||||
push rbp
|
||||
push r8
|
||||
push r9
|
||||
push r10
|
||||
push r11
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
mov rdi, rsp
|
||||
call irq_dispatch
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop r11
|
||||
pop r10
|
||||
pop r9
|
||||
pop r8
|
||||
pop rbp
|
||||
pop rdi
|
||||
pop rsi
|
||||
pop rdx
|
||||
pop rcx
|
||||
pop rbx
|
||||
pop rax
|
||||
add rsp, 16
|
||||
iretq
|
||||
|
||||
section .rodata
|
||||
align 8
|
||||
irq_stub_table:
|
||||
dq isr_stub_32, isr_stub_33, isr_stub_34, isr_stub_35
|
||||
dq isr_stub_36, isr_stub_37, isr_stub_38, isr_stub_39
|
||||
dq isr_stub_40, isr_stub_41, isr_stub_42, isr_stub_43
|
||||
dq isr_stub_44, isr_stub_45, isr_stub_46, isr_stub_47
|
||||
|
|
|
|||
189
kernel/arch/x86_64/keyboard.c
Normal file
189
kernel/arch/x86_64/keyboard.c
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
/* PS/2 scancode set-1 IRQ1 driver (see keyboard.h).
|
||||
*
|
||||
* IRQ wiring uses the live framework: keyboard_init() registers
|
||||
* keyboard_on_irq() via irq_register_handler() (idt.h) and unmasks
|
||||
* line 1 via pic_unmask() (pic.h), both feature-detected with
|
||||
* __has_include so this file still compiles if either header is
|
||||
* absent (registration then falls back to caller duty). The driver
|
||||
* itself never executes `sti`: IF stays cleared and IRQs only fire
|
||||
* after the integrator remaps the PIC, installs the IRQ gates, and
|
||||
* enables interrupts. 8259 IRQ1 -> vector 33.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "keyboard.h"
|
||||
|
||||
/* Port I/O owned by include/io.h; no local inb duplicate. */
|
||||
#include "io.h"
|
||||
|
||||
#if defined(__has_include)
|
||||
#if __has_include("idt.h")
|
||||
#include "idt.h"
|
||||
#define NADIR_HAS_IRQ_REG 1
|
||||
#endif
|
||||
#if __has_include("pic.h")
|
||||
#include "pic.h"
|
||||
#define NADIR_HAS_PIC 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define KBD_DATA 0x60U /* PS/2 data port: one scancode per IRQ1 */
|
||||
#define KBD_BUF_SIZE 128U /* power of two so wrap is cheap */
|
||||
|
||||
#define SC_RELEASE 0x80U /* high bit set = key release, ignore */
|
||||
#define SC_EXTENDED 0xE0U /* prefix for arrows/extended keys, ignore */
|
||||
#define SC_LSHIFT 0x2AU
|
||||
#define SC_RSHIFT 0x36U
|
||||
#define SC_CAPS 0x3AU
|
||||
|
||||
/* US set-1 codes without Shift (0 = no printable mapping). Backspace
|
||||
* 0x0E stays '\b' and Enter 0x1C stays '\n' so editing works raw. */
|
||||
static const char k_unshifted[128] = {
|
||||
[0x02] = '1', [0x03] = '2', [0x04] = '3', [0x05] = '4',
|
||||
[0x06] = '5', [0x07] = '6', [0x08] = '7', [0x09] = '8',
|
||||
[0x0A] = '9', [0x0B] = '0', [0x0C] = '-', [0x0D] = '=',
|
||||
[0x0E] = '\b', [0x0F] = '\t',
|
||||
[0x10] = 'q', [0x11] = 'w', [0x12] = 'e', [0x13] = 'r',
|
||||
[0x14] = 't', [0x15] = 'y', [0x16] = 'u', [0x17] = 'i',
|
||||
[0x18] = 'o', [0x19] = 'p', [0x1A] = '[', [0x1B] = ']',
|
||||
[0x1C] = '\n',
|
||||
[0x1E] = 'a', [0x1F] = 's', [0x20] = 'd', [0x21] = 'f',
|
||||
[0x22] = 'g', [0x23] = 'h', [0x24] = 'j', [0x25] = 'k',
|
||||
[0x26] = 'l', [0x27] = ';', [0x28] = '\'', [0x29] = '`',
|
||||
[0x2B] = '\\',
|
||||
[0x2C] = 'z', [0x2D] = 'x', [0x2E] = 'c', [0x2F] = 'v',
|
||||
[0x30] = 'b', [0x31] = 'n', [0x32] = 'm', [0x33] = ',',
|
||||
[0x34] = '.', [0x35] = '/', [0x39] = ' ',
|
||||
};
|
||||
|
||||
/* Same codes with Shift held (letters stay lowercase here; the Shift
|
||||
* + CapsLock XOR in keyboard_on_irq() uppercases them instead). */
|
||||
static const char k_shifted[128] = {
|
||||
[0x02] = '!', [0x03] = '@', [0x04] = '#', [0x05] = '$',
|
||||
[0x06] = '%', [0x07] = '^', [0x08] = '&', [0x09] = '*',
|
||||
[0x0A] = '(', [0x0B] = ')', [0x0C] = '_', [0x0D] = '+',
|
||||
[0x0E] = '\b', [0x0F] = '\t',
|
||||
[0x10] = 'Q', [0x11] = 'W', [0x12] = 'E', [0x13] = 'R',
|
||||
[0x14] = 'T', [0x15] = 'Y', [0x16] = 'U', [0x17] = 'I',
|
||||
[0x18] = 'O', [0x19] = 'P', [0x1A] = '{', [0x1B] = '}',
|
||||
[0x1C] = '\n',
|
||||
[0x1E] = 'A', [0x1F] = 'S', [0x20] = 'D', [0x21] = 'F',
|
||||
[0x22] = 'G', [0x23] = 'H', [0x24] = 'J', [0x25] = 'K',
|
||||
[0x26] = 'L', [0x27] = ':', [0x28] = '"', [0x29] = '~',
|
||||
[0x2B] = '|',
|
||||
[0x2C] = 'Z', [0x2D] = 'X', [0x2E] = 'C', [0x2F] = 'V',
|
||||
[0x30] = 'B', [0x31] = 'N', [0x32] = 'M', [0x33] = '<',
|
||||
[0x34] = '>', [0x35] = '?', [0x39] = ' ',
|
||||
};
|
||||
|
||||
/* Single-producer (IRQ) / single-consumer (poll) ring. `volatile` stops
|
||||
* the compiler caching head/tail across the IRQ boundary; on one core
|
||||
* with byte-sized index traffic no lock is needed because the producer
|
||||
* only writes head (+buf slot) and the consumer only writes tail. */
|
||||
static char k_buf[KBD_BUF_SIZE];
|
||||
static volatile uint8_t k_head;
|
||||
static volatile uint8_t k_tail;
|
||||
/* IRQ-only state: touched solely in keyboard_on_irq(), so plain. */
|
||||
static uint8_t k_shift;
|
||||
static uint8_t k_caps;
|
||||
static uint8_t k_e0;
|
||||
|
||||
/* Queue helpers below; port I/O comes from io.h. */
|
||||
|
||||
/* Drop-newest on full: keeps the consumer's prefix intact for editing. */
|
||||
static void enqueue(char c)
|
||||
{
|
||||
uint8_t next = (uint8_t)((k_head + 1) % KBD_BUF_SIZE);
|
||||
if (next == k_tail) {
|
||||
return;
|
||||
}
|
||||
k_buf[k_head] = c;
|
||||
k_head = next;
|
||||
}
|
||||
|
||||
void keyboard_init(void)
|
||||
{
|
||||
k_head = 0;
|
||||
k_tail = 0;
|
||||
k_shift = 0;
|
||||
k_caps = 0;
|
||||
k_e0 = 0;
|
||||
#ifdef NADIR_HAS_IRQ_REG
|
||||
/* Route vector 33 -> keyboard_on_irq(); without idt.h this stays
|
||||
* caller duty (manual irq_register_handler call). */
|
||||
irq_register_handler(1, keyboard_on_irq);
|
||||
#endif
|
||||
#ifdef NADIR_HAS_PIC
|
||||
pic_unmask(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
void keyboard_on_irq(void)
|
||||
{
|
||||
uint8_t code = inb(KBD_DATA);
|
||||
|
||||
if (code == SC_EXTENDED) {
|
||||
k_e0 = 1;
|
||||
return;
|
||||
}
|
||||
if (k_e0 != 0) {
|
||||
/* Extended make/break: swallow, maps to 0. */
|
||||
k_e0 = 0;
|
||||
return;
|
||||
}
|
||||
/* only Shift release changes state the rest is ignored. */
|
||||
if ((code & SC_RELEASE) != 0) {
|
||||
uint8_t make = (uint8_t)(code & (uint8_t)~SC_RELEASE);
|
||||
if (make == SC_LSHIFT || make == SC_RSHIFT) {
|
||||
k_shift = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (code == SC_LSHIFT || code == SC_RSHIFT) {
|
||||
k_shift = 1;
|
||||
return;
|
||||
}
|
||||
/* CapsLock toggles on press only */
|
||||
if (code == SC_CAPS) {
|
||||
k_caps = (uint8_t)(k_caps == 0 ? 1 : 0);
|
||||
return;
|
||||
}
|
||||
if (code >= sizeof(k_unshifted)) {
|
||||
return;
|
||||
}
|
||||
char base = k_unshifted[code];
|
||||
if (base == '\0') {
|
||||
return;
|
||||
}
|
||||
char out = base;
|
||||
if (base >= 'a' && base <= 'z') {
|
||||
/* Shift XOR CapsLock uppercases letters. */
|
||||
if ((k_shift != 0) != (k_caps != 0)) {
|
||||
out = (char)(base - 'a' + 'A');
|
||||
}
|
||||
} else if (k_shift != 0) {
|
||||
char shifted = k_shifted[code];
|
||||
if (shifted != '\0') {
|
||||
out = shifted;
|
||||
}
|
||||
} else if (k_caps != 0) {
|
||||
/* CapsLock alone must not shift digits/symbols */
|
||||
}
|
||||
enqueue(out);
|
||||
}
|
||||
|
||||
int keyboard_has_key(void)
|
||||
{
|
||||
return k_head != k_tail;
|
||||
}
|
||||
|
||||
char keyboard_dequeue(void)
|
||||
{
|
||||
if (k_head == k_tail) {
|
||||
return 0;
|
||||
}
|
||||
char c = k_buf[k_tail];
|
||||
k_tail = (uint8_t)((k_tail + 1) % KBD_BUF_SIZE);
|
||||
return c;
|
||||
}
|
||||
90
kernel/arch/x86_64/pic.c
Normal file
90
kernel/arch/x86_64/pic.c
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/* Nadir 8259 PIC driver: cascaded master/slave remap + EOI + masking.
|
||||
*
|
||||
* Runs in 64-bit long mode at Ring 0 (CPL 0) with the stage-2 GDT.
|
||||
* Privileged instructions: `outb`/`inb` via io.h (trap #GP if CPL >
|
||||
* IOPL); no `cli`/`sti`/`lidt`.
|
||||
* Fault behavior: no CPU fault possible a wrong ICW order only
|
||||
* misroutes IRQs.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "io.h"
|
||||
#include "pic.h"
|
||||
|
||||
/* Init control words: ICW1 starts init, ICW2 sets the vector base,
|
||||
* ICW3 wires the cascade (master IRQ2 <-> slave identity 2), ICW4
|
||||
* selects 8086 mode. io_wait() after each byte gives the slow 8259
|
||||
* time to latch it. */
|
||||
#define PIC_ICW1_INIT 0x11U
|
||||
#define PIC_ICW3_MASTER_SLAVE_LINE 0x04U
|
||||
#define PIC_ICW3_SLAVE_ID 0x02U
|
||||
#define PIC_ICW4_8086 0x01U
|
||||
|
||||
void pic_remap(void)
|
||||
{
|
||||
/* WHY this exact order: the 8259 latches ICW2-4 only while in init
|
||||
* mode, and the cascade identities must match or slave IRQs never
|
||||
* reach the CPU. Mask-all at the end keeps devices quiet until
|
||||
* their driver calls pic_unmask(). */
|
||||
outb(PIC1_CMD, PIC_ICW1_INIT);
|
||||
io_wait();
|
||||
outb(PIC2_CMD, PIC_ICW1_INIT);
|
||||
io_wait();
|
||||
outb(PIC1_DATA, PIC_IRQ0_VECTOR);
|
||||
io_wait();
|
||||
outb(PIC2_DATA, PIC_IRQ8_VECTOR);
|
||||
io_wait();
|
||||
outb(PIC1_DATA, PIC_ICW3_MASTER_SLAVE_LINE);
|
||||
io_wait();
|
||||
outb(PIC2_DATA, PIC_ICW3_SLAVE_ID);
|
||||
io_wait();
|
||||
outb(PIC1_DATA, PIC_ICW4_8086);
|
||||
io_wait();
|
||||
outb(PIC2_DATA, PIC_ICW4_8086);
|
||||
io_wait();
|
||||
outb(PIC1_DATA, 0xFFU);
|
||||
io_wait();
|
||||
outb(PIC2_DATA, 0xFFU);
|
||||
io_wait();
|
||||
}
|
||||
|
||||
void pic_eoi(uint8_t irq)
|
||||
{
|
||||
if (irq >= PIC_IRQ_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (irq >= 8U) {
|
||||
outb(PIC2_CMD, PIC_EOI);
|
||||
}
|
||||
outb(PIC1_CMD, PIC_EOI);
|
||||
}
|
||||
|
||||
void pic_mask(uint8_t irq)
|
||||
{
|
||||
uint16_t port;
|
||||
uint8_t mask;
|
||||
|
||||
if (irq >= PIC_IRQ_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
port = (irq < 8U) ? PIC1_DATA : PIC2_DATA;
|
||||
mask = inb(port) | (uint8_t)(1U << (irq & 7U));
|
||||
outb(port, mask);
|
||||
}
|
||||
|
||||
void pic_unmask(uint8_t irq)
|
||||
{
|
||||
uint16_t port;
|
||||
uint8_t mask;
|
||||
|
||||
if (irq >= PIC_IRQ_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
port = (irq < 8U) ? PIC1_DATA : PIC2_DATA;
|
||||
mask = inb(port) & (uint8_t) ~(1U << (irq & 7U));
|
||||
outb(port, mask);
|
||||
}
|
||||
95
kernel/arch/x86_64/pit.c
Normal file
95
kernel/arch/x86_64/pit.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/* 8254 PIT timer driver (see pit.h).
|
||||
*
|
||||
* Programs channel 0 in mode 3 (square-wave generator) at 100Hz on IRQ0.
|
||||
*
|
||||
* Why 1193182Hz base: the PIT input clock is the IBM-PC crystal frequency
|
||||
* (14.31818MHz / 12); every PC-compatible PIT ticks at this rate, so the
|
||||
* divisor for N Hz is always 1193182 / N.
|
||||
* Why mode 3: square-wave output stays high/low for half the period and
|
||||
* retriggers the IRQ line cleanly each cycle; mode 2 (rate generator)
|
||||
* emits narrow pulses that are fine too, but mode 3 is the conventional
|
||||
* choice for periodic system ticks on channel 0.
|
||||
* Why volatile + no locking: single-core kernel; s_ticks is written only
|
||||
* in IRQ0 context and read in mainline code. `volatile` keeps the compiler
|
||||
* from caching the counter across the hlt loop, and on x86-64 an aligned
|
||||
* 64-bit load/store is atomic, so no spinlock is needed while IF gates the
|
||||
* writer.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "idt.h"
|
||||
#include "io.h"
|
||||
#include "pit.h"
|
||||
|
||||
#define PIT_CMD 0x43U
|
||||
#define PIT_CH0 0x40U
|
||||
/* Ch0, lobyte/hibyte access, mode 3 square-wave, binary (not BCD). */
|
||||
#define PIT_CMD_CH0_MODE3 0x36U
|
||||
|
||||
/* Compile-time guard: a zero divisor would mean 65536 to the hardware and
|
||||
* silently run at ~18Hz instead of PIT_HZ. Frequency is fixed on purpose. */
|
||||
_Static_assert(PIT_DIVISOR != 0, "PIT divisor must not be zero");
|
||||
_Static_assert(PIT_DIVISOR <= 0xFFFFU, "PIT divisor must fit in 16 bits");
|
||||
|
||||
/* Port I/O is owned by include/io.h (single owner); irq_register_handler
|
||||
* is owned by idt.c via include/idt.h. */
|
||||
|
||||
/* Monotonic tick counter: written in IRQ context. */
|
||||
static volatile uint64_t s_ticks;
|
||||
/* pit_init is idempotent; re-entry after the first programming is a no-op. */
|
||||
static uint8_t s_initialized;
|
||||
|
||||
void pit_init(void)
|
||||
{
|
||||
uint16_t divisor;
|
||||
uint8_t lo;
|
||||
uint8_t hi;
|
||||
|
||||
if (s_initialized != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* No cli/sti here on purpose: latch programming is two plain outb
|
||||
* writes (safe with IF=0 at boot) and interrupt policy belongs to the
|
||||
* caller (kmain integrator), which unmasks IRQ0 and stis afterwards. */
|
||||
divisor = (uint16_t)PIT_DIVISOR;
|
||||
lo = (uint8_t)(divisor & 0xFFU);
|
||||
hi = (uint8_t)((divisor >> 8) & 0xFFU);
|
||||
|
||||
/* Latch order matters: command byte first selects ch0 + lobyte/hibyte
|
||||
* mode, then the divisor follows low byte first, high byte second. */
|
||||
outb(PIT_CMD, PIT_CMD_CH0_MODE3);
|
||||
outb(PIT_CH0, lo);
|
||||
outb(PIT_CH0, hi);
|
||||
|
||||
/* Strong idt.h registration: IRQ0 -> vector 32 wires pit_on_tick. */
|
||||
irq_register_handler(PIT_IRQ, pit_on_tick);
|
||||
|
||||
s_initialized = 1;
|
||||
}
|
||||
|
||||
void pit_on_tick(void)
|
||||
{
|
||||
/* Single increment async-safe, no logging or port I/O here. The PIC
|
||||
* EOI is owned by the IRQ dispatcher not this driver. */
|
||||
s_ticks++;
|
||||
}
|
||||
|
||||
uint64_t pit_ticks(void)
|
||||
{
|
||||
return s_ticks;
|
||||
}
|
||||
|
||||
void pit_sleep(uint64_t ticks)
|
||||
{
|
||||
uint64_t start = s_ticks;
|
||||
|
||||
/* hlt (not a spin loop) so the CPU sleeps until the next interrupt.
|
||||
* Assumes IF=1 and IRQ0 unmasked; with IF=0 this would halt forever,
|
||||
* which is why pit_init leaves interrupt policy to the caller. The
|
||||
* unsigned delta keeps working if s_ticks wraps mid-sleep. */
|
||||
while ((s_ticks - start) < ticks) {
|
||||
__asm__ volatile("hlt");
|
||||
}
|
||||
}
|
||||
65
kernel/arch/x86_64/serial.c
Normal file
65
kernel/arch/x86_64/serial.c
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/* COM1 16550 UART polling log (see serial.h). */
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "io.h"
|
||||
#include "serial.h"
|
||||
|
||||
/* Register offsets from the COM1 base. */
|
||||
#define COM1_BASE 0x3F8U
|
||||
#define COM_DATA 0x3F8U /* THR/RBR (DLAB=0) / divisor low (DLAB=1) */
|
||||
#define COM_IER 0x3F9U /* interrupt enable / divisor high (DLAB=1) */
|
||||
#define COM_FIFO 0x3FAU /* FIFO control */
|
||||
#define COM_LCTL 0x3FBU /* line control + DLAB bit 7 */
|
||||
#define COM_MCTL 0x3FCU /* modem control */
|
||||
#define COM_LSR 0x3FDU /* line status: bit 5 = THR empty, bit 0 = RX ready */
|
||||
|
||||
#define LSR_TX_EMPTY 0x20U /* THR holds no byte: safe to write */
|
||||
#define LSR_RX_READY 0x01U /* RBR holds a byte (reserved for future RX) */
|
||||
|
||||
/* Port I/O owned by include/io.h; no local inb/outb duplicates. */
|
||||
|
||||
void serial_init(void)
|
||||
{
|
||||
/* No UART interrupts: pure polling, so the 8259/IDT stays untouched. */
|
||||
outb(COM_IER, 0x00);
|
||||
/* DLAB on to program the divisor. */
|
||||
outb(COM_LCTL, 0x80);
|
||||
/* Divisor 3 = 115200/3 = 38400 baud. */
|
||||
outb(COM_DATA, 0x03);
|
||||
outb(COM_IER, 0x00);
|
||||
/* 8N1: 8 bits, no parity, one stop bit; DLAB back off. */
|
||||
outb(COM_LCTL, 0x03);
|
||||
/* FIFO on, clear both queues, 14-byte threshold. */
|
||||
outb(COM_FIFO, 0xC7);
|
||||
/* DTR+RTS on, OUT2 on so the UART drives interrupts (unused). */
|
||||
outb(COM_MCTL, 0x0B);
|
||||
}
|
||||
|
||||
void serial_putchar(char c)
|
||||
{
|
||||
/* Spin until THR is empty; hangs only if hardware is absent/wedged,
|
||||
* which is why callers gate on serial_ok() when unsure. */
|
||||
while ((inb(COM_LSR) & LSR_TX_EMPTY) == 0) {
|
||||
/* Poll. */
|
||||
}
|
||||
outb(COM_DATA, (uint8_t)c);
|
||||
}
|
||||
|
||||
void serial_puts(const char *s)
|
||||
{
|
||||
if (s == 0) {
|
||||
return;
|
||||
}
|
||||
while (*s != '\0') {
|
||||
serial_putchar(*s);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
int serial_ok(void)
|
||||
{
|
||||
/* An absent port reads back 0xFF (open bus); a real 16550 never
|
||||
* reports all status bits set at once, so that means no UART. */
|
||||
return inb(COM_LSR) != 0xFFU;
|
||||
}
|
||||
|
|
@ -2,30 +2,157 @@
|
|||
*
|
||||
* Called once from the stage-2 entry stub after the CPU is in 64-bit long
|
||||
* mode with a valid stack, zeroed BSS and identity-mapped memory. Runs at
|
||||
* Ring 0 (CPL 0) interrupts disabled. Installs the exception IDT first
|
||||
* so from then on a fault prints diagnostics instead of triple-faulting.
|
||||
* It never returns; on return the caller halts the CPU.
|
||||
* Ring 0 (CPL 0) with IF=0 on entry under the stage-2 GDT (CS 0x18);
|
||||
* `-mno-red-zone` keeps IRQ pushes off the red zone so interrupt frames
|
||||
* are safe. Installs the exception IDT first so from then on a fault
|
||||
* prints diagnostics instead of triple-faulting. `sti` runs exactly once
|
||||
* after every device is programmed; the main loop keeps IF=1 and sleeps
|
||||
* in `hlt` (which wakes on each IRQ) — never `cli`-parks, or timer ticks
|
||||
* and keystrokes would stop. It never returns; on return the caller halts.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "console.h"
|
||||
#include "idt.h"
|
||||
#include "keyboard.h"
|
||||
#include "pic.h"
|
||||
#include "pit.h"
|
||||
#include "serial.h"
|
||||
|
||||
/* A value that only comes out right if the CPU truly executes 64-bit
|
||||
* arithmetic: every nibble survives the shifts above 32 bits. */
|
||||
#define PROOF_VALUE 0x0123456789ABCDEFULL
|
||||
|
||||
/* Print an unsigned decimal via console_putchar (no libc printf). */
|
||||
static void print_dec(uint64_t v)
|
||||
{
|
||||
char buf[20];
|
||||
int n = 0;
|
||||
|
||||
if (v == 0) {
|
||||
console_putchar('0');
|
||||
return;
|
||||
}
|
||||
while (v != 0 && n < (int)sizeof(buf)) {
|
||||
buf[n++] = (char)('0' + (v % 10U));
|
||||
v /= 10U;
|
||||
}
|
||||
while (n > 0) {
|
||||
n--;
|
||||
console_putchar(buf[n]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mirror one echoed keystroke to COM1 when the UART looks present. */
|
||||
static void serial_echo(char c, int ok)
|
||||
{
|
||||
char tmp[2];
|
||||
|
||||
if (ok == 0) {
|
||||
return;
|
||||
}
|
||||
if (c == '\b') {
|
||||
/* Erase on a serial terminal: back, blank, back. */
|
||||
serial_puts("\b \b");
|
||||
} else if (c == '\n') {
|
||||
serial_puts("\r\n");
|
||||
} else {
|
||||
tmp[0] = c;
|
||||
tmp[1] = '\0';
|
||||
serial_puts(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void kmain(void)
|
||||
{
|
||||
int serial_present;
|
||||
uint64_t last_uptime;
|
||||
|
||||
console_clear();
|
||||
console_puts("Hello World from Nadir Kernel !\n");
|
||||
console_puts("Running C code in 64-bit long mode (Ring 0).\n");
|
||||
|
||||
/* WHY this order: pic_remap must precede idt_install_irqs so IRQ
|
||||
* gates land on valid remapped vectors 32-47 (pre-remap they would
|
||||
* overlap CPU exceptions 0-15 and a tick would look like a fault);
|
||||
* devices (serial/PIT/keyboard) are programmed before sti so no IRQ
|
||||
* fires into a half-wired handler; pic_remap masks everything and
|
||||
* each device opts in via pic_unmask, so stray lines stay silent. */
|
||||
idt_init();
|
||||
console_puts("IDT installed (vectors 0-31).\n");
|
||||
|
||||
pic_remap();
|
||||
console_puts("PIC remapped (IRQ 0-15 -> vectors 32-47, masked).\n");
|
||||
|
||||
idt_install_irqs();
|
||||
console_puts("IRQ gates installed (vectors 32-47).\n");
|
||||
|
||||
serial_init();
|
||||
serial_present = serial_ok();
|
||||
console_puts("Serial COM1 38400 8N1 ready.\n");
|
||||
if (serial_present != 0) {
|
||||
serial_puts("Hello World from Nadir Kernel !\n");
|
||||
serial_puts("Serial COM1 38400 8N1 ready.\n");
|
||||
}
|
||||
|
||||
pit_init();
|
||||
console_puts("PIT 100Hz on IRQ0 ready.\n");
|
||||
if (serial_present != 0) {
|
||||
serial_puts("PIT 100Hz on IRQ0 ready.\n");
|
||||
}
|
||||
|
||||
keyboard_init();
|
||||
console_puts("Keyboard PS/2 IRQ1 ready.\n");
|
||||
if (serial_present != 0) {
|
||||
serial_puts("Keyboard PS/2 IRQ1 ready.\n");
|
||||
}
|
||||
|
||||
/* Explicit opt-in for the two live lines; idempotent even though
|
||||
* keyboard_init already unmasked IRQ1. */
|
||||
pic_unmask(0);
|
||||
pic_unmask(1);
|
||||
|
||||
__asm__ volatile("sti");
|
||||
console_puts("Interrupts enabled (IRQs 0-1).\n");
|
||||
if (serial_present != 0) {
|
||||
serial_puts("Interrupts enabled (IRQs 0-1).\n");
|
||||
}
|
||||
|
||||
console_puts("64-bit proof value: ");
|
||||
console_puthex64(PROOF_VALUE);
|
||||
console_puts("\nHalting.\n");
|
||||
console_puts("\nHalting in hlt loop (timer + keyboard live).\n");
|
||||
|
||||
/* Main loop: hlt sleeps until the next IRQ (IF=1 throughout), then
|
||||
* one poll pass prints uptime ~1/s and drains echoed keys. */
|
||||
last_uptime = 0;
|
||||
for (;;) {
|
||||
uint64_t now;
|
||||
|
||||
__asm__ volatile("hlt");
|
||||
now = pit_ticks();
|
||||
if ((now - last_uptime) >= 100U) {
|
||||
uint64_t secs = now / 100U;
|
||||
uint64_t tenths = (now / 10U) % 10U;
|
||||
|
||||
last_uptime = now;
|
||||
console_puts("uptime ");
|
||||
print_dec(secs);
|
||||
console_puts(".");
|
||||
print_dec(tenths);
|
||||
console_puts("s (ticks ");
|
||||
console_puthex64(now);
|
||||
console_puts(")\n");
|
||||
if (serial_present != 0) {
|
||||
serial_puts("uptime (see VGA for value)\n");
|
||||
}
|
||||
}
|
||||
while (keyboard_has_key() != 0) {
|
||||
char c = keyboard_dequeue();
|
||||
|
||||
/* console_putchar erases on '\b' and wraps on '\n'. */
|
||||
console_putchar(c);
|
||||
serial_echo(c, serial_present);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue