4 Commits

Author SHA1 Message Date
bcd09a898c Add parallel port handling in parallel.c
Implement parallel port support with detection and configuration.
Add LPT port handling in parallel.c
Implement LPT port detection and configuration functions.
2026-02-04 20:53:22 +01:00
5cf2549d58 Add parallel.h header for LPT device management
This creates the header file for a parallel port driver
2026-02-02 13:18:31 -08:00
56faa3143d Merge pull request #109 from vmttmv/bl-e820
BL: Query and store e820 memory map
2026-02-02 07:49:09 -08:00
vmttmv
4fa82854dd BL: Query and store e820 memory map
- bl stage 1: move gdt+pm setup to stage 2 (avoids mode switching)
- bl stage 2: at entry query e820 table from bios, then setup gdt+pm
- kernel/memmap: pad map entry to 24 bytes, as exported by bl
- kernel/memmap: map copy (gbowne1)
- Makefile: facilitate placing the memory map at a known location
- Update bl docs
2026-01-28 18:08:31 +02:00
8 changed files with 268 additions and 53 deletions

View File

@@ -8,8 +8,13 @@ OBJCOPY = i386-elf-objcopy
BUILD_DIR = build
CROSS_DIR = cross
DISK_IMG = $(BUILD_DIR)/disk.img
STAGE2_ADDR = 0x7e00
STAGE2_SIZE = 2048
# Place the memory map (e820) past stage2 bl in memory
MEMMAP_BASE = $(shell echo $$(($(STAGE2_ADDR) + $(STAGE2_SIZE))))
KERNEL_C_SRC = $(wildcard kernel/*.c)
KERNEL_ASM_SRC = $(wildcard kernel/*.asm)
KERNEL_OBJ = $(patsubst kernel/%.c, $(BUILD_DIR)/%.o, $(KERNEL_C_SRC))
@@ -29,7 +34,7 @@ stage1: $(BUILD_DIR)
# NOTE: Stage2 final size should be checked against `$(STAGE2_SIZE)` by the build system to avoid an overflow.
# Alternatively, convey the final stage2 size through other means to stage1.
stage2: $(BUILD_DIR)
$(AS) $(ASFLAGS) -o $(BUILD_DIR)/stage2.o bootloader/stage2.asm
$(AS) $(ASFLAGS) -DMEMMAP_BASE=$(MEMMAP_BASE) -o $(BUILD_DIR)/stage2.o bootloader/stage2.asm
$(CC) -std=c11 -ffreestanding -nostdlib -nostdinc -fno-stack-protector -m32 -Iklibc/include -g -c -o $(BUILD_DIR)/stage2_load.o bootloader/stage2_load.c
$(LD) -Tbootloader/stage2.ld -melf_i386 -o $(BUILD_DIR)/$@.elf $(BUILD_DIR)/stage2.o $(BUILD_DIR)/stage2_load.o
$(OBJCOPY) -O binary $(BUILD_DIR)/$@.elf $(BUILD_DIR)/$@.bin
@@ -39,7 +44,7 @@ $(BUILD_DIR)/asm_%.o: kernel/%.asm
$(AS) $(ASFLAGS) -o $@ $<
$(BUILD_DIR)/%.o: kernel/%.c
$(CC) -std=c11 -ffreestanding -nostdlib -nostdinc -fno-stack-protector -m32 -Iklibc/include -g -c -o $@ $<
$(CC) -DMEMMAP_BASE=$(MEMMAP_BASE) -std=c11 -ffreestanding -nostdlib -nostdinc -fno-stack-protector -m32 -Iklibc/include -g -c -o $@ $<
$(BUILD_DIR)/klibc/%.o: klibc/src/%.c
$(CC) -std=c11 -ffreestanding -nostdlib -nostdinc -fno-stack-protector -m32 -Iklibc/include -g -c -o $@ $<

View File

@@ -11,16 +11,16 @@ Bootloader documentation for ClassicOS
## Stage 1 (`stage1.asm`)
Responsible for loading the second stage using BIOS routines, and switching to protected mode.
- Queries CHS parameters from BIOS
- Loads the second stage bootloader (2048 B) to `0x7c00`
- Sets up a GDT with descriptor entries for code and data both covering the whole 32-bit address space
- Enables A20
- Set CR0.PE (enable protected mode) and jump to stage 2
- Jumps to stage2
## Stage 2 (`stage2.asm, stage2_load.c`)
- Read and store E820 memory map from BIOS
- Sets up a GDT with descriptor entries for code and data both covering the whole 32-bit address space
- Set CR0.PE (enable protected mode)
- Set up segment registers
- Load the kernel ELF header
- Parse the program headers, and load all `PT_LOAD` segments from disk

View File

@@ -40,11 +40,8 @@ _start:
call enable_a20
jc a20_error ; Jump if A20 enable fails
; Setup Global Descriptor Table
call setup_gdt
; Switch to protected mode and jump to second stage at 0x08:0x7E00
call switch_to_pm
; Jump to s2
jmp 0x7e00
disk_error:
mov si, disk_error_msg
@@ -241,30 +238,6 @@ check_a20:
clc ; Clear carry flag to indicate success
ret
; ----------------------------------------------------------------
gdt_start:
dq 0x0000000000000000 ; Null descriptor
dq 0x00CF9A000000FFFF ; 32-bit code segment (selector 0x08)
dq 0x00CF92000000FFFF ; 32-bit data segment (selector 0x10)
dq 0x00009A000000FFFF ; 16-bit code segment for real mode (selector 0x18)
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
gdt_end:
setup_gdt:
lgdt [gdt_descriptor]
ret
; ----------------------------------------------------------------
switch_to_pm:
cli
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:0x7E00 ; jump to S2
; ----------------------------------------------------------------
print_string_16:
.loop:

View File

@@ -1,10 +1,80 @@
[BITS 32]
global _start
global ata_lba_read
extern load_kernel
%define e820_magic 0x534d4150 ; "SMAP"
%define e820_entry_size 24
%define e820_max_entries 128
; ----------------------------------------------------------------
; Real mode
; ----------------------------------------------------------------
[BITS 16]
_start:
call read_e820
call setup_gdt
call switch_to_pm
read_e820:
xor ebx, ebx
mov es, bx
mov di, MEMMAP_BASE+4 ; ES=0 DI=MEMMAP_BASE+4
xor bp, bp ; Keeping count in bp
.e820_loop:
mov eax, 0xe820
mov ecx, e820_entry_size
mov edx, e820_magic
int 0x15
jc .done ; Error?
cmp eax, e820_magic ; Verify "SMAP"
jne .done
test ecx, ecx ; Skip 0-sized entries
jz .skip
add di, e820_entry_size ; Advance write addr
inc bp ; Increment count
cmp bp, e820_max_entries ; Stop if we're at capacity
jae .done
.skip:
test ebx, ebx
jne .e820_loop
.done:
mov [MEMMAP_BASE], bp ; Store count
ret
setup_gdt:
lgdt [gdt_descriptor]
ret
switch_to_pm:
cli
mov eax, cr0
or eax, 1
mov cr0, eax
jmp 0x08:pm_entry
e820_count:
dw 0
gdt_start:
dq 0x0000000000000000 ; Null descriptor
dq 0x00CF9A000000FFFF ; 32-bit code segment (selector 0x08)
dq 0x00CF92000000FFFF ; 32-bit data segment (selector 0x10)
dq 0x00009A000000FFFF ; 16-bit code segment for real mode (selector 0x18)
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
gdt_end:
; ----------------------------------------------------------------
; Protected mode
; ----------------------------------------------------------------
[BITS 32]
pm_entry:
; Set up segments
; Data segments
mov ax, 0x10
@@ -18,9 +88,8 @@ _start:
mov ax, 0x08
mov cs, ax
; Stack (must be identity-mapped)
; Stack
mov esp, 0x90000
call load_kernel
jmp eax

View File

@@ -1,19 +1,16 @@
#include "memmap.h"
#define BOOTLOADER_MEMMAP_COUNT_ADDR MEMMAP_BASE
#define BOOTLOADER_MEMMAP_ADDR (MEMMAP_BASE + 4)
uint32_t get_memory_map(memory_map_entry_t *map, uint32_t max_entries) {
// Read the number of entries found by the bootloader
uint32_t entries_found = *(uint32_t*)BOOTLOADER_MEMMAP_COUNT_ADDR;
memory_map_entry_t *bios_data = (memory_map_entry_t*)BOOTLOADER_MEMMAP_ADDR;
uint32_t count = 0;
if (max_entries >= 1) {
map[count].base_addr = 0x00000000;
map[count].length = 0x0009FC00;
map[count].type = 1;
count++;
}
if (max_entries >= 2) {
map[count].base_addr = 0x00100000;
map[count].length = 0x1FF00000;
map[count].type = 1;
while (count < entries_found && count < max_entries) {
map[count] = bios_data[count];
count++;
}

View File

@@ -7,6 +7,7 @@ typedef struct {
uint64_t base_addr;
uint64_t length;
uint32_t type;
uint32_t ext;
} __attribute__((packed)) memory_map_entry_t;
uint32_t get_memory_map(memory_map_entry_t *map, uint32_t max_entries);

130
kernel/parallel.c Normal file
View File

@@ -0,0 +1,130 @@
#include "parallel.h"
#include "io.h"
#include "irq.h"
#include "serial.h" // or your print/terminal for debug
// Standard PC LPT base addresses
static const uint16_t lpt_base_addrs[LPT_MAX_PORTS] = {
0x378, // LPT1
0x278 // LPT2
};
lpt_device_t lpt_devices[LPT_MAX_PORTS];
// Register offsets
#define LPT_DATA(base) (base + 0)
#define LPT_STATUS(base) (base + 1)
#define LPT_CONTROL(base) (base + 2)
// STATUS bits
// bit 7: Busy (inverted), 6: Ack, 5: Paper Out, 4: Select, 3: Error
// CONTROL bits
// bit 0: Strobe, 1: Auto Linefeed, 2: Init, 3: Select In, 5: Bidirectional (PS/2)
// Simple presence check: write/read control & status
static bool lpt_detect(uint16_t base) {
uint8_t orig_ctrl = inb(LPT_CONTROL(base));
outb(LPT_CONTROL(base), orig_ctrl ^ 0x0F);
uint8_t new_ctrl = inb(LPT_CONTROL(base));
outb(LPT_CONTROL(base), orig_ctrl);
// If bits changed as expected, port likely exists
if (((orig_ctrl ^ new_ctrl) & 0x0F) == 0x0F) {
return true;
}
return false;
}
static void lpt_configure_bidir(uint16_t base, bool enable) {
uint8_t ctrl = inb(LPT_CONTROL(base));
if (enable) {
ctrl |= (1 << 5); // Set bidirectional bit (PS/2)
} else {
ctrl &= ~(1 << 5);
}
outb(LPT_CONTROL(base), ctrl);
}
void lpt_set_mode(lpt_port_t port, lpt_mode_t mode) {
if (port < 0 || port >= LPT_MAX_PORTS) return;
if (!lpt_devices[port].present) return;
uint16_t base = lpt_devices[port].base;
switch (mode) {
case LPT_MODE_COMPAT:
lpt_configure_bidir(base, false);
break;
case LPT_MODE_BIDIR:
lpt_configure_bidir(base, true);
break;
case LPT_MODE_EPP:
// TODO: EPP requires chipset support & config
// For now, just enable bidir as a baseline
lpt_configure_bidir(base, true);
break;
case LPT_MODE_ECP:
// TODO: ECP requires FIFO, DMA, and ECR register
// Stub for future implementation
lpt_configure_bidir(base, true);
break;
}
lpt_devices[port].mode = mode;
}
void lpt_write_byte(lpt_port_t port, uint8_t value) {
if (port < 0 || port >= LPT_MAX_PORTS) return;
if (!lpt_devices[port].present) return;
uint16_t base = lpt_devices[port].base;
// Wait until not busy (bit 7 is inverted busy)
while (!(inb(LPT_STATUS(base)) & 0x80))
;
outb(LPT_DATA(base), value);
// Pulse strobe
uint8_t ctrl = inb(LPT_CONTROL(base));
outb(LPT_CONTROL(base), ctrl | 0x01);
outb(LPT_CONTROL(base), ctrl & ~0x01);
}
uint8_t lpt_read_byte(lpt_port_t port) {
if (port < 0 || port >= LPT_MAX_PORTS) return 0xFF;
if (!lpt_devices[port].present) return 0xFF;
uint16_t base = lpt_devices[port].base;
// In bidirectional mode, data register is input
return inb(LPT_DATA(base));
}
// IRQ hook: you wire this into your IRQ handler for the LPT IRQ (usually 7 or 5)
void lpt_irq_handler(lpt_port_t port) {
// For now, just a stub. Later:
// - read status
// - acknowledge interrupt
// - wake waiting writer/reader
(void)port;
}
// Initialize all LPT ports
void lpt_init_all(void) {
for (int i = 0; i < LPT_MAX_PORTS; i++) {
lpt_devices[i].base = lpt_base_addrs[i];
lpt_devices[i].present = lpt_detect(lpt_devices[i].base);
lpt_devices[i].mode = LPT_MODE_COMPAT;
lpt_devices[i].irq = 0; // You can fill this if you parse BIOS/PCI/ACPI
if (lpt_devices[i].present) {
serial_write("LPT detected at base 0x");
// use your print_hex here if you want
}
}
// If you want interrupt-driven I/O:
// - Map LPT IRQ (usually 7 for LPT1, 5 for LPT2) in your PIC/IRQ layer
// - In your IRQ handler, call lpt_irq_handler(port)
}

40
kernel/parallel.h Normal file
View File

@@ -0,0 +1,40 @@
#ifndef PARALLEL_H
#define PARALLEL_H
#include <stdint.h>
#include <stdbool.h>
typedef enum {
LPT_PORT_NONE = -1,
LPT1_PORT = 0,
LPT2_PORT = 1,
LPT_MAX_PORTS = 2
} lpt_port_t;
typedef enum {
LPT_MODE_COMPAT = 0, // Standard (SPP)
LPT_MODE_BIDIR, // PS/2 bidirectional
LPT_MODE_EPP, // IEEE 1284 EPP
LPT_MODE_ECP // IEEE 1284 ECP
} lpt_mode_t;
typedef struct {
uint16_t base; // Base I/O address (e.g., 0x378, 0x278)
bool present; // Detected
lpt_mode_t mode; // Current mode
uint8_t irq; // IRQ line (if known/used)
} lpt_device_t;
extern lpt_device_t lpt_devices[LPT_MAX_PORTS];
void lpt_init_all(void);
void lpt_set_mode(lpt_port_t port, lpt_mode_t mode);
// Simple polled I/O
void lpt_write_byte(lpt_port_t port, uint8_t value);
uint8_t lpt_read_byte(lpt_port_t port);
// IRQ-driven hook (you implement the handler logic)
void lpt_irq_handler(lpt_port_t port);
#endif