feat(ps2): work in progress
+ 232
- 97
in this commit ps2 driver is in irq module (makes no sense), but ps2 was already a file

this will be part of cleanup

@@ -13,6 +13,7 @@ reboot:
 		jmp word 0xffff:0x0000
 
 ; void clear_screen()
+global clear_screen
 clear_screen:
 	.clear:
 		mov ax, 0x0700		; Entire screen

...
@@ -31,6 +32,7 @@ clear_screen:
 		ret
 
 ; void print_string(ds:si string)
+global print_string
 print_string:
 	.print_char:
 		; Gets a character and compares it with NULL

...
@@ -51,6 +53,7 @@ print_string:
 		ret
 
 ; void read_disk(cx sector, al number-to-read, es:bx into)
+global read_disk
 read_disk:
 	.read:
 		push ax

...
@@ -72,6 +75,7 @@ read_disk:
 		call reboot
 
 ; (ch cylinder, cl sector, dh head) calculate_chs(cx LBA-sector)
+global calculate_chs
 calculate_chs:
 	sectorsPerTrack: equ 36
 	headsPerCylinder: equ 2

@@ -3,7 +3,7 @@ def say_hello
   puts "Hello from Crystal!"
 end
 
-
+# @deprecated: use KernelShim.outb
 # def outb(port : UInt16, value : UInt8)
 #   asm (%(outb %al, %dx) : : "a"(value), "Nd"(port))
 # end

@@ -1,12 +1,19 @@
 bits 32
 
+extern kearly
 extern kmain
 
-global set_cursor_pos
-global hide_cursor
-global show_cursor
+extern irq_dispatcher_c
+extern pic_send_eoi
+
+; void outb(uint16, uint8)
+global outb
+outb:
+  out dx, al
+  ret
 
-; Inputs: rdi = row, rsi = col
+; void set_cursor_pos(int32, int32)
+global set_cursor_pos
 set_cursor_pos:
   push    ebp
   mov     ebp, esp

...
@@ -42,6 +49,7 @@ set_cursor_pos:
   ret
 
 ; void hide_cursor(void)
+global hide_cursor
 hide_cursor:
   ; Read: write index 0x0A, then write data with bit 5 set
   ; For simplicity we don't read current value; we set start = 0x20 (disabled)

...
@@ -56,6 +64,7 @@ hide_cursor:
   ret
 
 ; void show_cursor(void)
+global show_cursor
 show_cursor:
   ; Restore a typical cursor shape: start = 0 (scanline start)
   mov     dx, 0x3D4

...
@@ -77,12 +86,34 @@ show_cursor:
 
   ret
 
+section .text
+
+global irq_common_entry
+irq_common_entry:
+    pushad
+    push eax
+    call irq_dispatcher_c
+    add  esp, 4
+    popad
+    ret
+
+global irq1_stub
+irq1_stub:
+    cli
+    push byte 1
+    call irq_common_entry
+    push byte 1
+    call pic_send_eoi
+    add  esp, 4
+    iret
+
 section .entry
 
 ; void start()
 global _start
 _start:
 	mov esp, kernelStackStart
+	call kearly
 	call kmain
 
 	.hang:

@@ -1,55 +1,53 @@
 require "./vga.cr"
 
-# IDT module
-module IDT
-  # require "crystal/kernel"  # core types
+ @[Packed]
+struct Entry
+  offset_low : UInt16
+  selector : UInt16
+  ist : UInt8
+  type_attr : UInt8
+  offset_mid : UInt16
+  offset_high : UInt32
+  zero : UInt32
+end
 
-  IDT_ENTRIES = 256
+@[Packed]
+struct Ptr
+  limit : UInt16
+  base : UInt64
+end
 
-  # Storage for the IDT entries
-  @idt = StaticArray(Entry, IDT_ENTRIES)
-
-  @[Packed]
-  struct Entry
-    offset_low : UInt16
-    selector : UInt16
-    ist : UInt8
-    type_attr : UInt8
-    offset_mid : UInt16
-    offset_high : UInt32
-    zero : UInt32
-  end
+lib KernelShim
+  # External functions from assembly
+  # fun lidt(ptr : Ptr): Nil
+  # fun isr_handler_common(): Nil
+end
 
-  @[Packed]
-  struct Ptr
-    limit : UInt16
-    base : UInt64
-  end
+# IDT module
+module IDT
 
-  # External functions from assembly
-  extern "C" def lidt(ptr : Ptr*): Nil
-    {% if flag?(:linux) %}
-    {% else %}
-    {% end %}
-  end
+  IDT_ENTRIES = 256
 
-  # IRQ handlers storage
-  @irq_handlers = StaticArray(Void*, IDT_ENTRIES)
+  # # Storage for the IDT entries
+  # @@idt : StaticArray(Entry, IDT_ENTRIES) = StaticArray(Entry, IDT_ENTRIES).new()
+  # property idt
 
-  extern "C" def isr_handler_common: Nil
+  # # IRQ handlers storage
+  # @@irq_handlers : StaticArray(Proc(Nil, Nil), IDT_ENTRIES) = StaticArray(Proc(Nil, Nil), IDT_ENTRIES).new()
+  # property irq_handlers
 
-  def self.register_irq(irq : Int32, handler : Void*) : Nil
-    if irq >= 0 && irq < IDT_ENTRIES
-      @irq_handlers[irq] = handler
-    end
-  end
+  # def self.register_irq(irq : Int32, handler : Proc(Nil, Nil)) : Nil
+  #   if irq >= 0 && irq < IDT_ENTRIES
+  #     @irq_handlers[irq] = handler
+  #   end
+  # end
 
   def self.init : Nil
-    ptr = Ptr.new
-    ptr.limit = (IDT_ENTRIES * Entry.size) - 1
-    ptr.base = getaddr(&@idt).to_u64
-    lidt(&ptr)
+    #ptr = Ptr.new
+    #ptr.limit = (IDT_ENTRIES * Entry.size) - 1
+    #ptr.base = getaddr(&@idt).to_u64
+    #lidt(&ptr)
 
-    VGA.putd("IDT initialized\n")
+    # VGA.puts("IDT initialized\n")
   end
 end

new file
kernel/irq.cr
@@ -0,0 +1,51 @@
+lib KernelShim
+  fun outb(port : UInt16, value : UInt8) : Void
+end
+
+module IRQ
+  IRQ_COUNT = 16
+  HANDLERS = StaticArray(Proc(UInt8, Void)?, IRQ_COUNT).new()
+  
+  def self.init
+    self.register_keyboard
+    puts "IRQ initialized!\n"
+  end
+  
+  private def self.register_keyboard
+    # todo: handle keyboard events
+    register(1, # IRQ 1 = keyboard
+    ->(scan_code : UInt8) {
+      if scan_code == 0x1C
+        puts "Enter\n"
+      end
+      # do smth
+      puts "keyboard event\n"
+    })
+  end
+
+  def self.register(irq : Int32, handler : Proc(UInt8, Void)) : Proc(UInt8, Void)?
+    return nil if irq < 0 || irq >= IRQ_COUNT
+    prev = HANDLERS[irq]
+    HANDLERS[irq] = handler
+    prev
+  end
+
+  def self.dispatcher(irq : UInt32)
+    i = irq.to_u8
+    h = HANDLERS[i]
+    h.call(i) if h
+  end
+end
+
+fun irq_dispatcher_c(irq : UInt32)
+  puts "irq_dispatcher_c\n"
+  IRQ.dispatcher(irq)
+end
+
+fun pic_send_eoi(irq : UInt32)
+  puts "pic_send_eoi\n"
+  if irq >= 8
+    KernelShim.outb(0xA0_u16, 0x20_u8)
+  end
+  KernelShim.outb(0x20_u16, 0x20_u8)
+end

@@ -1,7 +1,8 @@
-require "./tiny_alloc_i386.cr"
 require "./vga.cr"
+require "./tiny_alloc_i386.cr"
+require "./idt.cr"
+require "./irq.cr"
 # require "./pmm.cr"
-# require "./ps2.cr"
 
 lib LibBootstrap
   @[Packed]

...
@@ -17,18 +18,19 @@ fun kearly(info_ptr: LibBootstrap::StartInfo*)
 end
 
 fun kmain
-  # PMM.init # Physical Memory Init
-  # TinyAllocI386.init # Tiny Bump Allocator
-
   VGA.init
-
   VGA.puts(
     "[CrystalOS Kernel] Hello from Crystal.\n",
     color: VGA::Colors::BLACK_ON_CYAN,
   )
+
+  IDT.init
+  IRQ.init # Should be PS2 ?
+  TinyAllocI386.init
+  # PMM.init # Physical Memory Init
+
   puts("weeeeeeee\n")
 
-  VGA.puts("Hello world!\n \nWorks maybe?\n")
   VGA.puts("Can print on multiple lines\n", color: VGA::Colors::BLACK_ON_LIME)
   VGA.puts("\nWe still don't have a heap, but now the puts stores the last row and col so it can call the putchar method with the \x1b[32mright\x1b[0m col / row instead of overlaying subsequent calls on-top one of another (this is super long line to test word wrap).\n")
   VGA.puts "\nFeatures:\n"

...
@@ -44,6 +46,8 @@ fun kmain
 
   # VGA.set_cursor(0, 5)
   # VGA.hide_cursor
+  
+  # asm (%(sti))
 
   while true
     asm (%(hlt))

@@ -1,12 +1,11 @@
-# PMM module - Physical Memory Manager
 module PMM
   PAGE_SIZE = 4096
   KERNEL_HEAP_START = 0x200000_u64
   KERNEL_HEAP_SIZE = 16 * 1024 * 1024
 
-  TOTAL_PAGES = 40322 # KERNEL_HEAP_SIZE / PAGE_SIZE
+  TOTAL_PAGES = 4096 # KERNEL_HEAP_SIZE / PAGE_SIZE
 
-  @@page_bitmap = StaticArray(UInt64, 40322).new()
+  @@page_bitmap = StaticArray(UInt64, TOTAL_PAGES).new()
   @@heap_ptr = Pointer(UInt8).new(KERNEL_HEAP_START)
   @@heap_end = Pointer(UInt8).new(KERNEL_HEAP_START + KERNEL_HEAP_SIZE)
 

@@ -5,7 +5,7 @@ module PS2
 
   def self.init : Nil
     # Initialize PS/2 controller
-    VGA.puts("PS/2 initialized\n")
+    VGA.puts("PS/2 initialized!\n")
   end
 
 end

kernel/tiny_alloc_i386.cr
@@ -1,6 +1,4 @@
-# Minimal bump allocator + memset/memcpy for i386 bare-metal Crystal
-
-# Exports C symbols: malloc, free, memset, memcpy, calloc
+require "./vga.cr"
 
 # 32-bit addresses/sizes (see linker.ld)
 HEAP_START = 0x00108000_u32

...
@@ -44,11 +42,14 @@ end
 # top-level var binding (becomes a global/static)
 HEAP_STATE = HeapState.new
 
+# Minimal bump allocator for i386 (freestand)
 module TinyAllocI386
   def self.init
     return if HEAP_STATE.inited
     HEAP_STATE.heap_ptr = HEAP_STATE.heap_base
     HEAP_STATE.inited = true
+    
+    VGA.puts("TinyAllocI386 initialized!\n")
   end
 
   def self.align_up32(v : UInt32, align : UInt32) : UInt32

...
@@ -98,39 +99,3 @@ module TinyAllocI386
     p
   end
 end
-
-# Expose C symbols via a lib block (no @[Export] needed)
-lib CExports
-  fun malloc(size : UInt32) : Pointer(Void)
-  fun free(ptr : Pointer(Void))
-  fun memset(ptr : Pointer(Void), c : Int32, n : UInt32) : Pointer(Void)
-  fun memcpy(dest : Pointer(Void), src : Pointer(Void), n : UInt32) : Pointer(Void)
-  fun calloc(nmemb : UInt32, size : UInt32) : Pointer(Void)
-end
-
-# Providd implementations
-def malloc(size : UInt32) : Pointer(Void)
-  TinyAllocI386.malloc32(size).as(Pointer(Void))
-end
-
-def free(ptr : Pointer(Void))
-  # no-op
-end
-
-def memset(ptr : Pointer(Void), c : Int32, n : UInt32) : Pointer(Void)
-  TinyAllocI386.memset_bytes(Pointer(UInt8).new(ptr.to_i), c.to_u8, n)
-  ptr
-end
-
-def memcpy(dest : Pointer(Void), src : Pointer(Void), n : UInt32) : Pointer(Void)
-  TinyAllocI386.memcpy_bytes(
-    Pointer(UInt8).new(dest.to_i),
-    Pointer(UInt8).new(src.to_i),
-    n
-  )
-  dest
-end
-
-def calloc(nmemb : UInt32, size : UInt32) : Pointer(Void)
-  TinyAllocI386.calloc32(nmemb, size).as(Pointer(Void))
-end

@@ -1,3 +1,4 @@
+require "./prelude/crystal_core/mem"
 require "./prelude/atomic"
 require "./prelude/primitives"
 

new file
prelude/crystal_core/mem.cr
@@ -0,0 +1,47 @@
+fun memset(dst : UInt8*, c : USize, n : USize) : Void*
+  r0 = r1 = r2 = 0
+  asm(
+    "cld\nrep stosb"
+          : "={al}"(r0), "={Di}"(r1), "={cx}"(r2)
+          : "{al}"(c.to_u8), "{Di}"(dst), "{cx}"(n)
+          : "volatile", "memory"
+  )
+  dst.as(Void*)
+end
+
+fun memcpy(dst : UInt8*, src : UInt8*, n : USize) : Void*
+  r0 = r1 = r2 = 0
+  asm(
+    "cld\nrep movsb"
+          : "={Di}"(r0), "={Si}"(r1), "={cx}"(r2)
+          : "{Di}"(dst), "{Si}"(src), "{cx}"(n)
+          : "volatile", "memory"
+  )
+  dst.as(Void*)
+end
+
+fun memcmp(s1 : UInt8*, s2 : UInt8*, n : Int32) : Int32
+  while n > 0 && (s1.value == s2.value)
+    s1 += 1
+    s2 += 1
+    n -= 1
+  end
+  return 0 if n == 0
+  (s1.value - s2.value).to_i32
+end
+
+fun memmove(dst : UInt8*, src : UInt8*, n : USize) : Void*
+  if src.address < dst.address
+    src += n.to_i64
+    dst += n.to_i64
+    until n == 0
+      dst -= 1
+      src -= 1
+      dst.value = src.value
+      n -= 1
+    end
+  else
+    memcpy dst, src, n
+  end
+  dst.as(Void*)
+end

prelude/crystal_core/string.cr
@@ -269,6 +269,34 @@ class String
   def hash(hasher)
     hasher.hash self
   end
+  
+  def interpolation(str)
+    parts = str.split(/\#\{({|})/)
+    result = ""
+    i = 0
+    while i < parts.size
+      if parts[i] == "{"
+        i += 1
+        expr = ""
+        depth = 1
+        while depth > 0
+          if parts[i] == "{"
+            depth += 1
+          elsif parts[i] == "}"
+            depth -= 1
+          end
+          expr += parts[i]
+          i += 1
+        end
+        expr = expr[1...-1] # Remove { and }
+        result += eval(expr).to_s
+      else
+        result += parts[i]
+        i += 1
+      end
+    end
+    result
+  end
 
   def to_s
     self

@@ -6,3 +6,10 @@ end
 def p!(str : String)
   VGA.puts str
 end
+
+# STDIN  = IO::FileDescriptor.new 0
+# STDOUT = IO::FileDescriptor.new 1
+# STDERR = IO::FileDescriptor.new 2
+# STDERR.buffer_size = 0
+
+# STDOUT.puts "hello from crystal\n"

@@ -65,7 +65,7 @@ echo -e "\nRunning..."
 
 use_vnc=${USE_VNC:-false}
 
-if [ "$use_VNC" = true ]
+if [ "$use_vnc" = true ]
 then
   qemu-system-i386 -drive if=floppy,index=0,format=raw,file=crystalos.img || exit 1
 else