OaSis OS driver

Drivers

This section documents the hardware boundary of OaSis. The networking internals page covers the complete PCI-to-ICMP path; the pages below cover the earlier device drivers.

Driver catalog

driver status Source
PCI configuration space Implemented for bus 0 src/kernel/drivers/pci.c
RTL8139 Ethernet Implemented for QEMU, polling mode src/kernel/drivers/rtl8139.c
Ethernet, ARP, IPv4, ICMP Implemented subset src/kernel/drivers/ethernet.c, arp.c, ip.c, icmp.c
PS/2 keyboard Implemented src/kernel/drivers/keyboard.c
PIT timer Implemented src/kernel/drivers/timer.c
VGA text mode Implemented src/kernel/core/vga.c
ATA PIO and block cache Implemented src/kernel/drivers/ata.c, block.c
PIC and port I/O Implemented src/kernel/drivers/pic.c, io.c

Networking

read Networking internals for PCI discovery, RTL8139 DMA, Ethernet frames, ARP resolution, IPv4 validation, ICMP echo, QEMU setup, and troubleshooting.

Legacy driver details

The sections below retain the detailed keyboard, timer, VGA, ATA, block-cache, PIC, and port-I/O notes from the original driver guide.

Keyboard (ps/2)

file: src/kernel/drivers/keyboard.c

Initialization

void keyboard_init(void) {
 (void)inb(KEYBOARD_DATA); // buang byte pending
 ctrl_pressed = 0;
 extended_key = 0;
}

Handler interrupt (irq1)

void keyboard_interrupt_handler(void) {
 uint8_t scancode = inb(KEYBOARD_DATA); // read from port 0x60

 if (scancode == 0xE0) { extended_key = 1; return; }
 if (extended_key) {
 // arrow keys, delete, home, end, pgup, pgdn
 extended_key = 0;
 if (scancode & 0x80) return; // release
 uint8_t key = ...; // konversi
 keyboard_buffer[write_pos] = key;
 write_pos = (write_pos + 1) % KEYBOARD_BUFFER_SIZE;
 return;
 }

 // shift
 if (scancode == 0x2A) { shift_pressed = 1; return; }
 if (scancode == 0xAA) { shift_pressed = 0; return; }
 // ctrl
 if (scancode == 0x1D) { ctrl_pressed = 1; return; }
 if (scancode == 0x9D) { ctrl_pressed = 0; return; }

 if (scancode & 0x80) return; // tombol release, cuekin

 const char *map = shift_pressed ? keymap_shift : keymap_us;
 char c = map[scancode];
 if (c != 0) {
 keyboard_buffer[write_pos] = c;
 write_pos = (write_pos + 1) % KEYBOARD_BUFFER_SIZE;
 }
}

Circular buffer

static uint8_t keyboard_buffer[KEYBOARD_BUFFER_SIZE]; // 
static volatile int read_pos = 0;
static volatile int write_pos = 0;

Keyboard_getchar

char keyboard_getchar(void) {
 while (read_pos == write_pos) {
 asm volatile("sti; hlt"); // tunggu interrupt
 }
 uint8_t key = keyboard_buffer[read_pos];
 read_pos = (read_pos + 1) % KEYBOARD_BUFFER_SIZE;
 return (char)key;
}

sti; hlt is atomic on x86: interrupts are enabled when the CPU halts, so a keyboard IRQ wakes the CPU.

Keymap

static const char keymap_us[128] = {
 0, 27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
 '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n', 0,
 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\',
 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, '*', 0, ' ',
};

Timer (pit)

file: src/kernel/drivers/timer.c

Initialization

void timer_init(uint32_t frequency) {
 uint32_t divisor = PIT_FREQUENCY / frequency; // 1193182 / 100 = 11931

 outb(PIT_CONTROL, 0x36); // channel 0, mode 2 (rate generator)
 outb(PIT_CHANNEL_0, divisor & 0xFF); // low byte
 outb(PIT_CHANNEL_0, (divisor >> 8) & 0xFF); // high byte

 pic_enable_irq(0); // enable the timer IRQ in the PIC
}

Handler

void timer_interrupt_handler(void) {
 ticks++;
 task_switch();
}

100hz -> fires every 10 ms. ticks counter can used for uptime.

Timer_get_ticks / timer_sleep

uint32_t timer_get_ticks(void) { return ticks; }

void timer_sleep(uint32_t ms) {
 uint32_t target = ticks + (ms / 10);
 while (ticks < target);
}

Vga text mode

file: src/kernel/core/vga.c

Memory

# Define VGA_MEMORY 0xB8000
static uint16_t* vga_buffer = (uint16_t*)VGA_MEMORY;

each character : char (low) + attribute (high). attribute: 4-bit foreground + 4-bit background.

Function

void vga_putc(char c); // write a character, handle \n, \b, scroll
void vga_print(const char* s); // print string
void vga_clear(void); // bersihin layar
void vga_set_color(uint8_t fg, uint8_t bg);
void vga_set_cursor(uint8_t x, uint8_t y);
void vga_write_char(uint8_t x, uint8_t y, char c, uint8_t color);
void vga_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, char c, uint8_t color);

Vga_putc

void vga_putc(char c) {
 if (c == '\n') { cursor_x = 0; cursor_y++; if (cursor_y >= 25) scroll(); return; }
 if (c == '\b') { if (cursor_x > 0) cursor_x--; buffer[...] = ' '; return; }

 vga_buffer[cursor_y * 80 + cursor_x] = c | (color << 8);
 cursor_x++;
 if (cursor_x >= 80) { cursor_x = 0; cursor_y++; if (cursor_y >= 25) scroll(); }
}

Scroll

move the screen up by several lines:

for (y = 1; y < 25; y++)
 for (x = 0; x < 80; x++)
 buffer[(y-1)*80 + x] = buffer[y*80 + x];
// clear last row
for (x = 0; x < 80; x++)
 buffer[24*80 + x] = ' ' | (color << 8);

Hardware cursor

program cursor position via crtc registers:

uint16_t pos = y * 80 + x;
outb(VGA_CTRL_REG, 0x0F); outb(VGA_DATA_REG, pos & 0xFF);
outb(VGA_CTRL_REG, 0x0E); outb(VGA_DATA_REG, (pos >> 8) & 0xFF);

Ata (ide) – pio mode

file: src/kernel/drivers/ata.c

Port

0x1F0: data port (16-bit)
0x1F2: sector count
0x1F3: lba low
0x1F4: lba mid
0x1F5: lba high
0x1F6: drive/head (0xE0 = master, 0xF0 = slave)
0x1F7: command/status

Read sector (pio, lba28)

ata_read_sector(uint32_t lba, void *buffer) {
 // pilih drive
 outb(0x1F6, 0xE0 | ((lba >> 24) & 0x0F));
 // set lba
 outb(0x1F2, 1); // sector count
 outb(0x1F3, lba & 0xFF);
 outb(0x1F4, (lba >> 8) & 0xFF);
 outb(0x1F5, (lba >> 16) & 0xFF);
 // command
 outb(0x1F7, 0x20); // read sectors with retry

 // polling status
 while (inb(0x1F7) & 0x80); // wait not busy
 while (!(inb(0x1F7) & 0x08)); // wait drq

 // read data (256 word = )
 for (int i = 0; i < 256; i++)
 ((uint16_t*)buffer)[i] = inw(0x1F0);
}

Block cache

file: src/kernel/drivers/block.c

entry cache for mengurangi akses disk directly.

typedef struct {
 uint32_t block_num;
 uint8_t data[BLOCK_SIZE]; // 
 int valid;
 int dirty;
} cache_entry_t;

static cache_entry_t cache[64];

Block_read

int block_read(uint32_t abs_block, uint8_t *buffer) {
 // cek cache
 for (int i = 0; i < 64; i++) {
 if (cache[i].valid && cache[i].block_num == abs_block) {
 memcpy(buffer, cache[i].data, BLOCK_SIZE);
 return 0;
 }
 }
 // cache miss -> read from disk through ATA
 ata_read_sector(abs_block, buffer);
 // store in the cache (evict if full)
 int slot = find_free_cache();
 cache[slot].block_num = abs_block;
 memcpy(cache[slot].data, buffer, BLOCK_SIZE);
 cache[slot].valid = 1;
 cache[slot].dirty = 0;
 return 0;
}

Block_write

int block_write(uint32_t abs_block, const uint8_t *buffer) {
 // find a free cache slot
 int slot = find_free_cache();
 cache[slot].block_num = abs_block;
 memcpy(cache[slot].data, buffer, BLOCK_SIZE);
 cache[slot].valid = 1;
 cache[slot].dirty = 1; // marked dirty, bakal di-flush nanti
 // write directly to disk as well
 ata_write_sector(abs_block, buffer);
 cache[slot].dirty = 0;
 return 0;
}

Block_flush

write all dirty cache entries to disk:

for (int i = 0; i < 64; i++)
 if (cache[i].valid && cache[i].dirty)
 ata_write_sector(cache[i].block_num, cache[i].data);

Pic

file: src/kernel/drivers/pic.c

Init

remap IRQ 0-7 to interrupts 32-39, irq 8-15 to 40-47:

outb(PIC_MASTER_CMD, ICW1_INIT | ICW1_ICW4); // 0x11
outb(PIC_SLAVE_CMD, ICW1_INIT | ICW1_ICW4);
outb(PIC_MASTER_DATA, 32); // icw2: master base 32
outb(PIC_SLAVE_DATA, 40); // icw2: slave base 40
outb(PIC_MASTER_DATA, 0x04); // icw3: slave on irq2
outb(PIC_SLAVE_DATA, 0x02); // icw3: slave id 2
outb(PIC_MASTER_DATA, ICW4_8086); // 0x01
outb(PIC_SLAVE_DATA, ICW4_8086);
outb(PIC_MASTER_DATA, 0xFF); // mask all IRQs first
outb(PIC_SLAVE_DATA, 0xFF);

Enable/disable irq

void pic_enable_irq(int irq) {
 uint16_t port = (irq < 8) ? PIC_MASTER_DATA : PIC_SLAVE_DATA;
 if (irq >= 8) irq -= 8;
 uint8_t mask = inb(port);
 mask &= ~(1 << irq); // clear bit = enable
 outb(port, mask);
}

Io ports

file: src/kernel/drivers/io.c

void outb(uint16_t port, uint8_t val) {
 asm volatile("outb %0, %1" : : "a"(val), "Nd"(port));
}
uint8_t inb(uint16_t port) {
 uint8_t ret;
 asm volatile("inb %1, %0" : "=a"(ret) : "Nd"(port));
 return ret;
}
On this page