From 14f831f5fe5a6491e5ab31c4001e5eaf1dd9a3b1 Mon Sep 17 00:00:00 2001
From: =?utf8?q?Duje=20Mihanovi=C4=87?= <duje.mihanovic@skole.hr>
Date: Sat, 14 May 2022 13:22:39 +0200
Subject: [PATCH] Add Intel 8254 driver and print elapsed seconds

---
 Makefile                             |  2 +-
 include/arch/x86/time/i8254.h        | 46 ++++++++++++++++++++++++++++
 include/arch/x86/tty.h               |  2 ++
 kernel/arch/x86/irq/sample_handler.c | 10 ++++++
 kernel/arch/x86/time/i8254.c         | 37 ++++++++++++++++++++++
 kernel/arch/x86/tty/tty.c            | 19 +++++++++++-
 kernel/kernel.c                      | 11 ++++---
 7 files changed, 121 insertions(+), 6 deletions(-)
 create mode 100644 include/arch/x86/time/i8254.h
 create mode 100644 kernel/arch/x86/time/i8254.c

diff --git a/Makefile b/Makefile
index f5a33b6..5373d0b 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@ export GIT_REV = $(shell git describe --long HEAD)
 
 CFLAGS = -std=gnu99 -g -Iinclude/arch/x86 -ffreestanding -DGIT_COMMIT=\"$(GIT_REV)\"
 
-KERNEL_OBJ = kernel/entry.o kernel/arch/x86/tty/tty.o kernel/drivers/irq/i8259a.o kernel/arch/x86/irq/idt.o kernel/arch/x86/irq/sample_handler.o kernel/kernel.o
+KERNEL_OBJ = kernel/entry.o kernel/arch/x86/tty/tty.o kernel/drivers/irq/i8259a.o kernel/arch/x86/irq/idt.o kernel/arch/x86/irq/sample_handler.o kernel/arch/x86/time/i8254.o kernel/kernel.o
 BOOTLOADER_OBJ = boot/x86/mbr boot/x86/vbr-fat32 boot/x86/stage3/LOADER.BIN
 
 default: kernel/kernel.elf bootloader
diff --git a/include/arch/x86/time/i8254.h b/include/arch/x86/time/i8254.h
new file mode 100644
index 0000000..18a8546
--- /dev/null
+++ b/include/arch/x86/time/i8254.h
@@ -0,0 +1,46 @@
+#ifndef X86_I8254_H
+#define X86_I8254_H
+
+#include <stdint.h>
+#include <io.h>
+#include <irq/i8259a.h>
+
+/* for setting reload value and reading current count */
+#define PORT_CHANNEL_0_DATA	0x40
+#define PORT_CHANNEL_1_DATA	0x41
+#define PORT_CHANNEL_2_DATA	0x42
+
+/* for configuring the channels */
+#define PORT_MODE_CMD_REG	0x43
+
+/* operating modes */
+#define INT_ON_TERM_CNT		0x0
+#define HW_RETRIGGER_ONESHOT	0x1
+#define RATE_GENERATOR		0x2
+#define SQUARE_WAVE_GENERATOR	0x3
+#define SW_TRIGGERED_STROBE	0x4
+#define HW_TRIGGERED_STROBE	0x5
+
+/* access modes */
+#define	ACMODE_COUNTER_LATCH_CMD	0x0
+#define ACMODE_LSB_ONLY			0x1
+#define ACMODE_MSB_ONLY			0x2
+#define ACMODE_LSB_MSB			0x3
+
+union mode_command {
+	struct {
+		unsigned bcd: 1, /* if set, uses BCD for reload value */
+			 opmode: 3, /* operating mode */
+			 acmode: 2, /* access mode */
+			 channel: 2; /* channel to configure */
+	} fields;
+	uint8_t command;
+};
+
+extern unsigned int ticks;
+extern uint16_t reload;
+
+extern void i8254_configure_channel(char channel, char opmode, uint16_t new_reload);
+extern void i8254_irq_enable();
+
+#endif
diff --git a/include/arch/x86/tty.h b/include/arch/x86/tty.h
index 42857ac..d1d3171 100644
--- a/include/arch/x86/tty.h
+++ b/include/arch/x86/tty.h
@@ -22,8 +22,10 @@
 
 extern void screen_clear(void);
 extern void kprint(const char *string, uint8_t color);
+extern void kprints(const char *string, uint8_t color);
 extern void kprintb(const uint8_t byte);
 extern void kprintw(const uint16_t word);
 extern void kprintd(const uint32_t dword);
+extern void kprintc(const char character, uint8_t color);
 extern int kprintdec(uint32_t num);
 #endif
diff --git a/kernel/arch/x86/irq/sample_handler.c b/kernel/arch/x86/irq/sample_handler.c
index f4f4a4b..be68298 100644
--- a/kernel/arch/x86/irq/sample_handler.c
+++ b/kernel/arch/x86/irq/sample_handler.c
@@ -2,6 +2,9 @@
 #include <irq/i8259a.h>
 #include <io.h>
 #include <stdint.h>
+#include <time/i8254.h>
+
+unsigned int ticks = 0;
 
 typedef uint32_t uword_t;
 
@@ -31,3 +34,10 @@ halt:
 	goto halt;
 }
 
+
+__attribute__((interrupt))
+void timer_tick(struct interrupt_frame *frame)
+{
+	pic_send_eoi(0);
+	ticks++;
+}
diff --git a/kernel/arch/x86/time/i8254.c b/kernel/arch/x86/time/i8254.c
new file mode 100644
index 0000000..d205f11
--- /dev/null
+++ b/kernel/arch/x86/time/i8254.c
@@ -0,0 +1,37 @@
+#include <time/i8254.h>
+
+uint16_t reload;
+
+void i8254_configure_channel(char channel, char opmode, uint16_t new_reload)
+{
+	union mode_command command;
+
+	command.fields.bcd = 0;
+	command.fields.channel = channel;
+	command.fields.acmode = ACMODE_LSB_MSB;
+	command.fields.opmode = opmode;
+
+	outb(PORT_MODE_CMD_REG, command.command);
+
+	switch (channel) {
+		case 0:
+			outb(PORT_CHANNEL_0_DATA, new_reload & 0xff);
+			outb(PORT_CHANNEL_0_DATA, new_reload >> 8);
+			break;
+		case 1:
+			outb(PORT_CHANNEL_1_DATA, new_reload & 0xff);
+			outb(PORT_CHANNEL_1_DATA, new_reload >> 8);
+			break;
+		case 2:
+			outb(PORT_CHANNEL_2_DATA, new_reload & 0xff);
+			outb(PORT_CHANNEL_2_DATA, new_reload >> 8);
+			break;
+	}
+
+	reload = new_reload;
+}
+
+void i8254_irq_enable()
+{
+	pic_unmask(0);
+}
diff --git a/kernel/arch/x86/tty/tty.c b/kernel/arch/x86/tty/tty.c
index 51686a3..83678b0 100644
--- a/kernel/arch/x86/tty/tty.c
+++ b/kernel/arch/x86/tty/tty.c
@@ -1,6 +1,7 @@
 #include <io.h>
 #include <tty.h>
 #include <stdint.h>
+#include <time/i8254.h>
 
 #define VGA_WIDTH 80
 #define VGA_HEIGHT 25
@@ -41,6 +42,14 @@ void scroll_up(void)
 }
 
 void kprint(const char *string, uint8_t color)
+{
+	kprintc('[', 0);
+	kprintdec(ticks / (1193182 / 65536));
+	kprints("] ", 0);
+	kprints(string, color);
+}
+
+void kprints(const char *string, uint8_t color)
 {
 	char next_char;
 	uint8_t vga_misc_output;
@@ -142,11 +151,18 @@ int kprintdec(uint32_t num)
 {
 	char buffer[11];
 	int digits = 10;
+
 	/* TODO: make an actual memset function to use instead of this */
 	for (int i=0; i<11; i++) {
 		buffer[i] = 0;
 	}
 
+	/* handle edge case */
+	if (num==0) {
+		*buffer = '0';
+		goto print;
+	}
+
 	/* put the numbers in the buffer */
 	for (int i=9; i>0 && num>0; i--) {
 		uint8_t currdigit = num%10;
@@ -169,6 +185,7 @@ int kprintdec(uint32_t num)
 		}
 	}
 
-	kprint(buffer, 0);
+print:
+	kprints(buffer, 0);
 	return digits;
 }
diff --git a/kernel/kernel.c b/kernel/kernel.c
index 24deeb9..35100f1 100644
--- a/kernel/kernel.c
+++ b/kernel/kernel.c
@@ -2,21 +2,25 @@
 #include <io.h>
 #include <irq/idt.h>
 #include <irq/i8259a.h>
+#include <time/i8254.h>
 #include <stdint.h>
 
 extern void double_fault(struct abort_frame *frame);
 extern void keyb_handler(struct interrupt_frame *frame);
+extern void timer_tick(struct interrupt_frame *frame);
 struct idt_descriptor idt[256] __attribute__((aligned(0x10)));
 struct idtr idtr __attribute__((aligned(0x10)));
 
 void kmain(void)
 {
+	ticks = 0;
 	screen_clear();
 	kprint("Welcome to Nameless OS!\nRunning revision: ", 0);
 	kprint(GIT_COMMIT, 0);
 	kprint("\n", 0);
 	kprint("Preparing IDT...\n", 0);
 	idt_set_descriptor(idt, 0x8, 0x8, (uint32_t) double_fault, IDT_TRAP_GATE, 0x0);
+	idt_set_descriptor(idt, 0x20, 0x8, (uint32_t) timer_tick, IDT_INTERRUPT_GATE, 0x0);
 	idt_set_descriptor(idt, 0x21, 0x8, (uint32_t) keyb_handler, IDT_INTERRUPT_GATE, 0x0);
 	kprint("IDT prepared, loading...\n", 0);
 	populate_idtr(&idtr, idt);
@@ -26,10 +30,9 @@ void kmain(void)
 	pic_mask_all();
 	pic_unmask(1);
 	asm volatile ("sti");
+	kprint("Setting up timer...\n", 0);
+	i8254_configure_channel(0, SQUARE_WAVE_GENERATOR, 0);
+	i8254_irq_enable();
 	kprint("All done\n", 0);
-	kprintdec(12345);
-	kprintc('\n');
-	kprintdec(6789);
-	kprintc('\n');
 	while(1);
 }
-- 
2.39.5