From 5616691621ea4da75fb29713851da31a15d4cb81 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 29 Jun 2021 21:48:14 -0700 Subject: ooh, timer works fine in isolation Acknowledging the interrupt is necessary. --- timer.subx | 416 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 timer.subx 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 -- cgit 1.4.1-2-gfad0