finished PIT implementation

This commit is contained in:
Chris Sanden
2026-04-15 15:49:11 +00:00
parent 46a91312f4
commit 9a213dcff2
12 changed files with 327 additions and 9 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,221 @@
# Assignment 4 Part 2: Programmable Interval Timer
## Overview
In this part of the assignment, work was done on adding a basic driver for the Programmable Interval Timer (PIT) to the `OSDev_18` kernel. The goal was to initialize the PIT, count timer ticks using IRQ0, and use those ticks to support both busy-wait sleeping and interrupt-based sleeping.
The PIT was configured to use channel 0 and generate regular timer interrupts at `1000 Hz`. This gives a simple timing model where one timer tick is approximately equal to one millisecond.
---
## Implemented Features
### PIT Header and Constants
- Added:
```c
include/kernel/pit.h
```
- Defined constants for:
- PIT command port
- PIT channel 0 port
- PIT base frequency
- target frequency
- calculated divider
- ticks-per-millisecond conversion
This provides a single place for PIT-related configuration and function declarations.
---
### PIT Initialization
- Implemented:
```c
void PitInitialize(void);
```
- Configures the PIT using:
```c
OutPortByte(PIT_CMD_PORT, 0x36);
```
- Loads the PIT divisor into channel 0:
- low byte first
- high byte second
- Registers the PIT interrupt handler on:
```c
IRQ0
```
This allows the kernel to start receiving regular timer interrupts from the PIT.
---
### Tick Counter
- Added a global tick counter:
```c
static volatile uint32_t pit_ticks = 0;
```
- Implemented a helper function:
```c
static uint32_t GetCurrentTick(void);
```
The tick counter is incremented every time IRQ0 is triggered. Since the PIT is configured to `1000 Hz`, this gives a simple way to measure elapsed time in milliseconds.
---
### PIT IRQ Handler
- Implemented:
```c
static void PitIrqHandler(struct Registers* regs);
```
- The handler:
- ignores the register argument
- increments `pit_ticks`
This keeps the timer interrupt path minimal and lets the rest of the kernel measure time by reading the current tick count.
---
### Busy-Wait Sleep
- Implemented:
```c
void SleepBusy(uint32_t milliseconds);
```
- Uses the current tick count to wait until enough time has passed
- Continuously checks the tick counter in a loop
This version works by actively spinning on the CPU, so it is simple but inefficient.
---
### Interrupt-Based Sleep
- Implemented:
```c
void SleepInterrupt(uint32_t ticks);
```
- Uses:
```c
__asm__ volatile ("sti; hlt");
```
inside a loop
- Re-checks the current tick count after each wake-up
This version is more efficient than busy waiting because the CPU is halted between interrupts instead of constantly polling in a tight loop.
---
### Kernel Integration
- Added:
```c
#include <kernel/pit.h>
```
to `kernel.c`
- Added:
```c
PitInitialize();
```
during kernel startup
- Added:
```c
SleepTest();
```
after the existing memory-allocation test code
This connects the PIT driver to the boot flow so the timer logic is available after interrupt setup is complete.
---
### Build System Update
- Added:
```c
src/pit.c
```
to `CMakeLists.txt`
This ensures the PIT implementation is compiled and linked into the kernel binary together with the rest of the source files.
---
### Terminal Output Fix
- Added the missing declaration for:
```c
void TerminalWriteHex(uint32_t num);
```
to `terminal.h`
This was necessary because `TerminalWriteHex()` was already implemented in `terminal.c` and used in several source files, but it was not exposed through the header.
---
## Testing & Verification
### Build Issues Resolved
During integration, several issues were found and corrected:
- incorrect inline assembly syntax:
```c
__asm__volatile
```
was corrected to:
```c
__asm__ volatile
```
- `printf()` calls were removed from PIT test code and replaced with terminal output functions
- the missing `TerminalWriteHex()` declaration in `terminal.h` was added
These changes allowed the kernel to build again with the PIT source included.
---
### Boot Image Verification
- Verified that rebuilding only:
```c
uiaos-kernel
```
updates `kernel.bin`, but does not update the bootable ISO image
- Confirmed that:
- the new `kernel.bin` contained the new PIT- and memory-related strings
- the older `kernel.iso` still contained `Hello, World!`
- Rebuilt the image using:
```c
uiaos-create-image
```
so QEMU would boot the updated kernel
This explained why old output was still appearing even after the source code had changed.
---
### Runtime Result
After rebuilding the bootable image, the kernel successfully booted into the updated code and displayed:
- paging setup output
- heap and allocation debug output
- the `memory1`, `memory2`, `memory3`, and `memory4` address prints
This confirms that the updated kernel binary is now being booted correctly.
The PIT sleep test loop was also verified at runtime. The kernel repeatedly prints the expected sleep messages for both:
- busy-wait sleeping
- interrupt-based sleeping
This shows that the PIT tick counter is advancing and that both sleep functions return as expected during execution.
During this test, one remaining issue was observed in the terminal output: once the VGA text buffer wraps back to the top of the screen, new text overwrites old text directly. A cleanup or scrolling improvement for the terminal was noted as a TODO in `kernel.c`.
---
## Conclusion
- Added a PIT driver skeleton to the kernel
- Configured PIT channel 0 for periodic timer interrupts
- Registered an IRQ0 handler and added a global tick counter
- Implemented both busy-wait and interrupt-based sleep functions
- Integrated PIT support into `kernel.c`
- Added `pit.c` to the build system
- Fixed the missing `TerminalWriteHex()` declaration
- Verified that the updated kernel is now booting from the rebuilt ISO image
- Verified that the PIT sleep test loop runs at runtime
This means the code structure for Part 2 is now present in the kernel and has been tested at runtime. The next step is to improve terminal behavior so repeated output does not overwrite earlier lines when the screen wraps.
---
## Next Step
Improve the terminal output behavior so repeated sleep-test messages scroll or clear cleanly instead of overwriting text when the screen wraps back to the top.

View File

@@ -70,6 +70,7 @@ add_executable(uiaos-kernel
src/malloc.c
src/memutils.c
src/memory.c
src/pit.c
)

View File

@@ -8,9 +8,9 @@ typedef struct {
uint32_t size;
} alloc_t;
void init_kernel_memory(uint32_t* kernel_end);
void InitKernelMemory(uint32_t* kernel_end);
void init_paging(void);
void InitPaging(void);
void paging_map_virtual_to_phys(uint32_t virt, uint32_t phys);
char* pmalloc(size_t size);
@@ -21,6 +21,6 @@ void* memcpy(void* dest, const void* src, size_t num);
void* memset (void* ptr, int value, size_t num);
void* memset16 (void* ptr, uint16_t value, size_t num);
void print_memory_layout(void);
void PrintMemoryLayout(void);
#endif

View File

@@ -0,0 +1,25 @@
#ifndef PIT_H
#define PIT_H
#include <libc/stdint.h>
#include <libc/stdbool.h>
#define PIT_CMD_PORT 0x43
#define PIT_CHANNEL0_PORT 0x40
#define PIT_CHANNEL1_PORT 0x41
#define PIT_CHANNEL2_PORT 0x42
#define PC_SPEAKER_PORT 0x61
#define PIT_DEFAULT_DIVISOR 0x4E20 //20000, gets just shy of 60Hz
#define PIC1_CMD_PORT 0x20
#define PIC1_DATA_PORT 0x21
#define PIT_BASE_FREQ 1193180
#define TARGET_FREQ 1000
#define DIVIDER (PIT_BASE_FREQ / TARGET_FREQ)
#define TICKS_PER_MS (TARGET_FREQ / TARGET_FREQ) // = 1, needed for converting ms into ticks
void PitInitialize();
void SleepInterrupt(uint32_t ticks);
void SleepBusy(uint32_t milliseconds);
void SleepTest();
#endif

View File

@@ -34,5 +34,6 @@ void TerminalPutChar(char c);
void TerminalWrite(const char* data, size_t size);
void TerminalWriteString(const char* data);
void TerminalWriteUInt(uint32_t num);
void TerminalWriteHex(uint32_t memory);
#endif

View File

@@ -5,6 +5,14 @@
#include <kernel/interrupt.h>
#include <kernel/keyboard.h>
#include <kernel/memory.h>
#include <kernel/pit.h>
/*TODO
clean up terminal - enable scrolling?
*/
extern uint32_t end;
@@ -12,12 +20,13 @@ void main(void) {
TerminalInitialize();
GdtInitialize();
IdtInitialize();
PitInitialize();
RegisterInterruptHandler(IRQ1, KeyboardHandler);
init_kernel_memory(&end);
init_paging();
print_memory_layout();
InitKernelMemory(&end);
InitPaging();
PrintMemoryLayout();
void* memory1 = malloc(48261);
void* memory2 = malloc(27261);
@@ -43,6 +52,8 @@ void main(void) {
TerminalWriteHex((uint32_t)memory4);
TerminalWriteString("\n");
SleepTest();
for (;;) {
__asm__ volatile("hlt");
}

View File

@@ -12,7 +12,7 @@ static uint32_t pheap_end = 0;
static uint8_t *pheap_desc = 0;
static uint32_t memory_used = 0;
void init_kernel_memory(uint32_t* kernel_end) {
void InitKernelMemory(uint32_t* kernel_end) {
uint32_t kernelEndAddr = (uint32_t)kernel_end;
last_alloc = kernelEndAddr + 0x1000;
@@ -28,7 +28,7 @@ void init_kernel_memory(uint32_t* kernel_end) {
TerminalWriteString("\n");
}
void print_memory_layout(void) {
void PrintMemoryLayout(void) {
TerminalWriteString("Memory used:");
TerminalWriteUInt(memory_used);
TerminalWriteString(" bytes\n");

View File

@@ -25,7 +25,7 @@ void paging_enable() {
asm volatile("mov %eax, %cr0");
}
void init_paging(void) {
void InitPaging(void) {
TerminalWriteString("Setting up paging\n");
page_directory = (uint32_t*)0x400000;

59
src/OSDev_18/src/pit.c Normal file
View File

@@ -0,0 +1,59 @@
#include <kernel/pit.h>
#include <kernel/interrupt.h>
#include <kernel/io.h>
#include <kernel/terminal.h>
static volatile uint32_t pit_ticks = 0; // Encasulated to pit.c to avoid accidental overwrite
static uint32_t GetCurrentTick(void){
return pit_ticks;
}
static void PitIrqHandler(struct Registers* regs){
(void)regs; // Required by interface, not needed in this implementation
pit_ticks++;
}
void PitInitialize(void){
uint16_t divisor = DIVIDER;
RegisterInterruptHandler(IRQ0, PitIrqHandler);
OutPortByte(PIT_CMD_PORT, 0x36);
OutPortByte(PIT_CHANNEL0_PORT, (uint8_t)(divisor & 0xFF));
OutPortByte(PIT_CHANNEL0_PORT, (uint8_t)((divisor >> 8) & 0xFF));
}
//More efficient way of sleeping compared to SleepBusy()
void SleepInterrupt(uint32_t ticks_to_wait){
uint32_t start_tick = GetCurrentTick();
uint32_t end_tick = start_tick + ticks_to_wait;
while(GetCurrentTick() < end_tick){
__asm__ volatile ("sti; hlt");
}
}
/* "Consumes" 100% CPU power to effectively put entire system into sleep
for (ticks_to_wait) amount of ticks / milliseconds */
void SleepBusy(uint32_t milliseconds){
uint32_t start_tick = GetCurrentTick();
uint32_t ticks_to_wait = milliseconds * TICKS_PER_MS;
while((GetCurrentTick() - start_tick) < ticks_to_wait){
/* Occupy the CPU by with handling a whole lot of nothing.
Also known as busy waiting */
}
}
void SleepTest(){
while (GetCurrentTick() < 15000) {
TerminalWriteString("Sleeping with busy-waiting (HIGH CPU).\n");
SleepBusy(1000);
TerminalWriteString("Slept using busy-waiting.\n");
TerminalWriteString("Sleeping with interrupts (LOW CPU).\n");
SleepInterrupt(1000);
TerminalWriteString("Slept using interrupts.\n");
}
}