about summary refs log tree commit diff stats
path: root/timer.subx
diff options
context:
space:
mode:
Diffstat (limited to 'timer.subx')
-rw-r--r--timer.subx416
1 files changed, 416 insertions, 0 deletions
diff --git a/timer.subx b/timer.subx
new file mode 100644
index 00000000..9095b323
--- /dev/null
+++ b/timer.subx
@@ -0,0 +1,416 @@
+# Example bootloader that handles just the timer interrupt.
+#
+# Code for the first few disk sectors that all programs in this directory need:
+#   - load sectors past the first (using BIOS primitives) since only the first is available by default
+#     - if this fails, print 'D' at top-left of screen and halt
+#   - initialize a minimal graphics mode
+#   - switch to 32-bit mode (giving up access to BIOS primitives)
+#   - set up a handler for keyboard events
+#   - jump to start of program
+
+# Code in this file needs to be more deliberate about the SubX facilities it
+# uses:
+#   - sigils only support 32-bit general-purpose registers, so don't work with segment registers or 16-bit or 8-bit registers
+#   - metadata like rm32 and r32 can sometimes misleadingly refer to only the bottom 16 bits of the register; pay attention to the register name
+#
+# While most of Mu is thoroughly tested, this file is not. I don't yet
+# understand hardware interfaces well enough to explain to others.
+
+# Memory map of a Mu computer:
+#   code: currently 4 tracks loaded from the primary disk to [0x00007c00, 0x00048600)
+#   stack: grows down from 0x02000000 to 0x01000000
+#   heap: [0x02000000, 0x08000000)
+#     see 120allocate.subx; Qemu initializes with 128MB RAM by default
+# Consult https://wiki.osdev.org/Memory_Map_(x86) before modifying any of
+# this. And don't forget to keep *stack-debug.subx in sync.
+
+== code
+
+## 16-bit entry point: 0x7c00
+
+# Upon reset, the IBM PC:
+#   - loads the first sector (512 bytes)
+#     from some bootable image (look for the boot-sector-marker further down this file)
+#     to the address range [0x7c00, 0x7e00)
+#   - starts executing code at address 0x7c00
+
+  fa/disable-interrupts
+
+  # initialize segment registers
+  b8/copy-to-ax 0/imm16
+  8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
+  8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
+  8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
+  8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
+
+  # Temporarily initialize stack to 0x00070000 in real mode.
+  # We don't read or write the stack before we get to 32-bit mode, but BIOS
+  # calls do. We need to move the stack in case BIOS initializes it to some
+  # low address that we want to write code into.
+  b8/copy-to-ax 0x7000/imm16
+  8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
+  bc/copy-to-esp 0/imm16
+
+  # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
+  # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
+  {
+    e4/read-port-into-al 0x64/imm8
+    a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
+    75/jump-if-!zero loop/disp8
+    b0/copy-to-al 0xd1/imm8
+    e6/write-al-into-port 0x64/imm8
+  }
+  {
+    e4/read-port-into-al 0x64/imm8
+    a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
+    75/jump-if-!zero loop/disp8
+    b0/copy-to-al 0xdf/imm8
+    e6/write-al-into-port 0x64/imm8
+  }
+
+  # load remaining sectors from first two tracks of disk into addresses [0x7e00, 0x17800)
+  b4/copy-to-ah 2/imm8/read-drive
+  # dl comes conveniently initialized at boot time with the index of the device being booted
+  b5/copy-to-ch 0/imm8/cylinder
+  b6/copy-to-dh 0/imm8/head                   # <====
+  b1/copy-to-cl 2/imm8/sector  # 1-based
+  b0/copy-to-al 0x7d/imm8/num-sectors  # 2*63 - 1 = 125
+  # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
+  bb/copy-to-bx 0/imm16
+  8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
+  bb/copy-to-bx 0x7e00/imm16                  # <====
+  cd/syscall 0x13/imm8/bios-disk-services
+  0f 82/jump-if-carry disk_error/disp16
+
+  # load two more tracks of disk into addresses [0x17800, 0x27400)
+  b4/copy-to-ah 2/imm8/read-drive
+  # dl comes conveniently initialized at boot time with the index of the device being booted
+  b5/copy-to-ch 0/imm8/cylinder
+  b6/copy-to-dh 2/imm8/head                   # <====
+  b1/copy-to-cl 1/imm8/sector  # 1-based
+  b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
+  # address to write sectors to = es:bx = 0x17800, contiguous with boot segment
+  bb/copy-to-bx 0x1780/imm16                  # <====
+  8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
+  bb/copy-to-bx 0/imm16
+  cd/syscall 0x13/imm8/bios-disk-services
+  0f 82/jump-if-carry disk_error/disp16
+
+  # load two more tracks of disk into addresses [0x27400, 0x37000)
+  b4/copy-to-ah 2/imm8/read-drive
+  # dl comes conveniently initialized at boot time with the index of the device being booted
+  b5/copy-to-ch 0/imm8/cylinder
+  b6/copy-to-dh 4/imm8/head                   # <====
+  b1/copy-to-cl 1/imm8/sector  # 1-based
+  b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
+  # address to write sectors to = es:bx = 0x27400, contiguous with boot segment
+  bb/copy-to-bx 0x2740/imm16                  # <====
+  8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
+  bb/copy-to-bx 0/imm16
+  cd/syscall 0x13/imm8/bios-disk-services
+  0f 82/jump-if-carry disk_error/disp16
+
+  # load two more tracks of disk into addresses [0x37000, 0x46c00)
+  b4/copy-to-ah 2/imm8/read-drive
+  # dl comes conveniently initialized at boot time with the index of the device being booted
+  b5/copy-to-ch 0/imm8/cylinder
+  b6/copy-to-dh 6/imm8/head                   # <====
+  b1/copy-to-cl 1/imm8/sector  # 1-based
+  b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
+  # address to write sectors to = es:bx = 0x37000, contiguous with boot segment
+  bb/copy-to-bx 0x3700/imm16                  # <====
+  8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
+  bb/copy-to-bx 0/imm16
+  cd/syscall 0x13/imm8/bios-disk-services
+  0f 82/jump-if-carry disk_error/disp16
+
+  # load two more tracks of disk into addresses [0x46c00, 0x56800)
+  b4/copy-to-ah 2/imm8/read-drive
+  # dl comes conveniently initialized at boot time with the index of the device being booted
+  b5/copy-to-ch 0/imm8/cylinder
+  b6/copy-to-dh 8/imm8/head                   # <====
+  b1/copy-to-cl 1/imm8/sector  # 1-based
+  b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
+  # address to write sectors to = es:bx = 0x46c00, contiguous with boot segment
+  bb/copy-to-bx 0x46c0/imm16                  # <====
+  8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
+  bb/copy-to-bx 0/imm16
+  cd/syscall 0x13/imm8/bios-disk-services
+  0f 82/jump-if-carry disk_error/disp16
+
+  # load two more tracks of disk into addresses [0x56800, 0x66400)
+  b4/copy-to-ah 2/imm8/read-drive
+  # dl comes conveniently initialized at boot time with the index of the device being booted
+  b5/copy-to-ch 0/imm8/cylinder
+  b6/copy-to-dh 0xa/imm8/head                 # <====
+  b1/copy-to-cl 1/imm8/sector  # 1-based
+  b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
+  # address to write sectors to = es:bx = 0x56800, contiguous with boot segment
+  bb/copy-to-bx 0x5680/imm16                  # <====
+  8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
+  bb/copy-to-bx 0/imm16
+  cd/syscall 0x13/imm8/bios-disk-services
+  0f 82/jump-if-carry disk_error/disp16
+
+  # reset es
+  bb/copy-to-bx 0/imm16
+  8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
+
+  # adjust video mode
+  b4/copy-to-ah 0x4f/imm8  # VBE commands
+  b0/copy-to-al 2/imm8  # set video mode
+  bb/copy-to-bx 0x4105/imm16  # 0x0105 | 0x4000
+                              # 0x0105 = graphics mode 1024x768x256
+                              #  (alternative candidate: 0x0101 for 640x480x256)
+                              # 0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively
+  cd/syscall 0x10/imm8/bios-video-services
+
+  # load information for the (hopefully) current video mode
+  # mostly just for the address to the linear frame buffer
+  b4/copy-to-ah 0x4f/imm8  # VBE commands
+  b0/copy-to-al 1/imm8  # get video mode info
+  b9/copy-to-cx 0x0105/imm16  # mode we requested
+  bf/copy-to-di Video-mode-info/imm16
+  cd/syscall 0x10/imm8/bios-video-services
+
+  ## switch to 32-bit mode
+  # load global descriptor table
+  # We can't refer to the label directly because SubX doesn't do the right
+  # thing for lgdt, so rather than make errors worse in most places we instead
+  # pin gdt_descriptor below.
+  0f 01 2/subop/lgdt 0/mod/indirect 6/rm32/use-disp16 0x7de0/disp16/gdt_descriptor
+  # enable paging
+  0f 20/<-cr 3/mod/direct 0/rm32/eax 0/r32/cr0
+  66 83 1/subop/or 3/mod/direct 0/rm32/eax 1/imm8  # eax <- or 0x1
+  0f 22/->cr 3/mod/direct 0/rm32/eax 0/r32/cr0
+  # far jump to initialize_32bit_mode that sets cs to offset 8 in the gdt in the process
+  # We can't refer to the label directly because SubX doesn't have syntax for
+  # segment selectors. So we instead pin initialize_32bit_mode below.
+  ea/jump-far-absolute 0x00087e00/disp32  # address 0x7e00 in offset 8 of the gdt
+
+disk_error:
+  # print 'D' to top-left of screen to indicate disk error
+  # *0xb8000 <- 0x0f44
+  bb/copy-to-bx 0xb800/imm16
+  8e/->seg 3/mod/direct 3/rm32/bx 3/r32/ds
+  b0/copy-to-al 0x44/imm8/D
+  b4/copy-to-ah 0x0f/imm8/white-on-black
+  bb/copy-to-bx 0/imm16
+  89/<- 0/mod/indirect 7/rm32/bx 0/r32/ax  # *ds:bx <- ax
+  # loop forever
+  {
+    eb/jump loop/disp8
+  }
+
+## GDT: 3 records of 8 bytes each
+== data 0x7de0
+gdt_descriptor:
+  0x17/imm16  # final index of gdt = size of gdt - 1
+  gdt_start/imm32/start
+
+gdt_start:
+# offset 0: gdt_null:  mandatory null descriptor
+  00 00 00 00 00 00 00 00
+# offset 8: gdt_code
+  ff ff  # limit[0:16]
+  00 00 00  # base[0:24]
+  9a  # 1/present 00/privilege 1/descriptor type = 1001b
+      # 1/code 0/conforming 1/readable 0/accessed = 1010b
+  cf  # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b
+      # limit[16:20] = 1111b
+  00  # base[24:32]
+# offset 16: gdt_data
+  ff ff  # limit[0:16]
+  00 00 00  # base[0:24]
+  92  # 1/present 00/privilege 1/descriptor type = 1001b
+      # 0/data 0/conforming 1/readable 0/accessed = 0010b
+  cf  # same as gdt_code
+  00  # base[24:32]
+# gdt_end:
+
+== boot-sector-marker 0x7dfe
+# final 2 bytes of boot sector
+55 aa
+
+## sector 2 onwards loaded by load_disk, not automatically on boot
+
+## 32-bit code from this point
+
+== code 0x7e00
+initialize_32bit_mode:
+  66 b8/copy-to-ax 0x10/imm16  # offset 16 from gdt_start
+  8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
+  8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
+  8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
+  8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
+  8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
+
+  bc/copy-to-esp 0x02000000/imm32
+
+  ## load interrupt handlers
+  # We can't refer to the label directly because SubX doesn't do the right
+  # thing for lidt, so rather than make errors worse in most places we instead
+  # pin idt_descriptor below.
+  0f 01 3/subop/lidt 0/mod/indirect 5/rm32/use-disp32 0x7f00/disp32/idt_descriptor
+
+  # enable timer IRQ0
+  b0/copy-to-al 0xfe/imm8  # disable mask for IRQ0
+  e6/write-al-into-port 0x21/imm8
+
+  fb/enable-interrupts
+
+  {
+    eb/jump loop/disp8
+  }
+
+== data 0x7f00
+idt_descriptor:
+  ff 03  # final index of idt = size of idt - 1
+  idt_start/imm32/start
+
+# interrupt descriptor table {{{
+# 32 entries of 8 bytes each
+idt_start:
+
+# entry 0
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+
+# By default, BIOS maps IRQ0-7 to interrupt vectors 8-15.
+# https://wiki.osdev.org/index.php?title=Interrupts&oldid=25102#Default_PC_Interrupt_Vector_Assignment
+
+# entry 8: https://wiki.osdev.org/Programmable_Interval_Timer
+  timer-interrupt-handler/imm16  # target[0:16]
+  8/imm16  # segment selector (gdt_code)
+  00  # unused
+  8e  # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
+  0/imm16  # target[16:32] -- timer-interrupt-handler must be within address 0x10000
+
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00
+# idt_end:
+# }}}
+
+== code
+
+timer-interrupt-handler:
+  # prologue
+  fa/disable-interrupts
+  60/push-all-registers
+  9c/push-flags
+  # acknowledge interrupt
+  b0/copy-to-al 0x20/imm8
+  e6/write-al-into-port 0x20/imm8
+  31/xor %eax 0/r32/eax
+  # ecx = *Current-color
+  8b/-> *Current-color 1/r32/ecx
+  # increment *Current-color
+  81 0/subop/add *Current-color 0x01010101/imm32
+  # eax = *Video-memory + 0x1200 (a few rows down from top, around middle of screen)
+  8b/-> *Video-memory-addr 0/r32/eax
+  05/add-to-eax 0x1200/imm32
+  89/copy *eax 1/r32/ecx
+  # eax += 0x400
+  05/add-to-eax 0x400/imm32
+  89/copy *eax 1/r32/ecx
+  # eax += 0x400
+  05/add-to-eax 0x400/imm32
+  89/copy *eax 1/r32/ecx
+  # eax += 0x400
+  05/add-to-eax 0x400/imm32
+  89/copy *eax 1/r32/ecx
+$timer-interrupt-handler:epilogue:
+  # epilogue
+  9d/pop-flags
+  61/pop-all-registers
+  fb/enable-interrupts
+  cf/return-from-interrupt
+
+== data
+
+Video-mode-info:
+# video mode info {{{
+  0/imm16  # attributes
+  00  # winA
+  00  # winB
+# 04
+  0/imm16  # granularity
+  0/imm16  # winsize
+# 08
+  0/imm16  # segmentA
+  0/imm16  # segmentB
+# 0c
+  0/imm32  # realFctPtr (who knows)
+# 10
+  0/imm16  # pitch
+  0/imm16  # Xres
+  0/imm16  # Yres
+  0/imm16  # Wchar Ychar
+# 18
+  00  # planes
+  00  # bpp
+  00  # banks
+  00  # memory_model
+# 1c
+  00  # bank_size
+  00  # image_pages
+  00  # reserved
+# 1f
+  0/imm16  # red_mask red_position
+  0/imm16  # green_mask green_position
+  0/imm16  # blue_mask blue_position
+  0/imm16  # rsv_mask rsv_position
+  00  # directcolor_attributes
+# 28
+Video-memory-addr:
+  0/imm32  # physbase
+
+# 2c
+# reserved for video mode info
+                                    00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+# }}}
+
+Current-color:
+  0/imm32
+
+# vim:ft=subx