From 0a41dae5f406d498b5c9ab1542cb5660e0d982f6 Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Wed, 1 Jun 2016 19:37:09 -0700 Subject: [PATCH 1/1] Initial checkin of toy OS project. --- Makefile | 5 + boot0/Makefile | 9 + boot0/boot0.asm | 347 ++++++++++++++++++++++++++ boot0/boot0.bin | Bin 0 -> 512 bytes boot1/Makefile | 14 ++ boot1/boot.com | Bin 0 -> 310 bytes boot1/boot1.asm | 113 +++++++++ boot1/hardware.asm | 42 ++++ boot1/memory.asm | 3 + boot1/video.asm | 81 ++++++ floppy.img | Bin 0 -> 99840 bytes inc/defs.asm | 33 +++ kernel/Makefile | 30 +++ kernel/defines | 6 + kernel/driver/keyboard.c | 37 +++ kernel/driver/video.c | 503 ++++++++++++++++++++++++++++++++++++++ kernel/hal/Makefile | 20 ++ kernel/hal/counters.c | 17 ++ kernel/hal/interrupts.c | 230 +++++++++++++++++ kernel/hal/interrupts.h | 92 +++++++ kernel/hal/intsupport.asm | 326 ++++++++++++++++++++++++ kernel/hal/io.c | 40 +++ kernel/hal/timer.c | 43 ++++ kernel/hal/timestamp.asm | 19 ++ kernel/hal/video.h | 15 ++ kernel/inc/constants.h | 50 ++++ kernel/inc/hal.h | 104 ++++++++ kernel/inc/kernel.h | 43 ++++ kernel/inc/macros.h | 43 ++++ kernel/inc/rtl.h | 33 +++ kernel/inc/types.h | 80 ++++++ kernel/init/Makefile | 20 ++ kernel/init/entry.asm | 23 ++ kernel/init/main.c | 67 +++++ kernel/kernel.bin | Bin 0 -> 13260 bytes kernel/kernel.map | 370 ++++++++++++++++++++++++++++ kernel/rtl/Makefile | 19 ++ kernel/rtl/memory.c | 29 +++ kernel/rtl/string.c | 138 +++++++++++ 39 files changed, 3044 insertions(+) create mode 100644 Makefile create mode 100755 boot0/Makefile create mode 100644 boot0/boot0.asm create mode 100644 boot0/boot0.bin create mode 100755 boot1/Makefile create mode 100644 boot1/boot.com create mode 100644 boot1/boot1.asm create mode 100644 boot1/hardware.asm create mode 100644 boot1/memory.asm create mode 100644 boot1/video.asm create mode 100644 floppy.img create mode 100644 inc/defs.asm create mode 100644 kernel/Makefile create mode 100644 kernel/defines create mode 100644 kernel/driver/keyboard.c create mode 100644 kernel/driver/video.c create mode 100644 kernel/hal/Makefile create mode 100644 kernel/hal/counters.c create mode 100644 kernel/hal/interrupts.c create mode 100644 kernel/hal/interrupts.h create mode 100644 kernel/hal/intsupport.asm create mode 100644 kernel/hal/io.c create mode 100644 kernel/hal/timer.c create mode 100644 kernel/hal/timestamp.asm create mode 100644 kernel/hal/video.h create mode 100644 kernel/inc/constants.h create mode 100644 kernel/inc/hal.h create mode 100644 kernel/inc/kernel.h create mode 100644 kernel/inc/macros.h create mode 100644 kernel/inc/rtl.h create mode 100644 kernel/inc/types.h create mode 100644 kernel/init/Makefile create mode 100644 kernel/init/entry.asm create mode 100644 kernel/init/main.c create mode 100755 kernel/kernel.bin create mode 100644 kernel/kernel.map create mode 100644 kernel/rtl/Makefile create mode 100644 kernel/rtl/memory.c create mode 100644 kernel/rtl/string.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ca5c681 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +SUBDIR += boot0 +SUBDIR += boot1 +SUBDIR += kernel + +.include diff --git a/boot0/Makefile b/boot0/Makefile new file mode 100755 index 0000000..162c810 --- /dev/null +++ b/boot0/Makefile @@ -0,0 +1,9 @@ +RM = /bin/rm +TARGET = boot0.bin +SOURCES = boot0.asm + +all: $(SOURCES) + nasm -f bin -o $(TARGET) $(SOURCES) ; \ + mformat -f 1440 -B boot0.bin Q: +clean: + $(RM) -f *.log *.core *~ *.gmon \#*\# $(TARGET) diff --git a/boot0/boot0.asm b/boot0/boot0.asm new file mode 100644 index 0000000..e31f126 --- /dev/null +++ b/boot0/boot0.asm @@ -0,0 +1,347 @@ +; +; This boot sector was based on VnutZ's example available online +; at http://www.geocities.com/mvea/bootstrap.htm. It also made use +; of info at http://support.microsoft.com/support/kb/articles/Q140/4/18.asp +; and the source code for geekos. +; +; It searches the root directory of a floppy for two files: boot.com and +; kernel.bin. If either is missing the boot fails. If they are found, they +; are loaded into memory at BOOT1SEG:BOOT1OFF and KERNSEG:KERNOFF. Then +; control is passed to the next stage bootloader, boot.com. +; +%include "../inc/defs.asm" + +[BITS 16] +[ORG 0] + +START: + jmp AROUND_DATA + +szOEM_ID db "SCOTTOS", 0 ; 8 bytes, null not required +wBytesPerSector dw 0x0200 ; bytes per sector +bSectorsPerCluster db 0x01 ; sectors per cluster +wReservedSectors dw 0x0001 ; reserved sectors (boot sector) +bTotalFATs db 0x02 ; num copies of the FAT +wMaxRootEntries dw 0x00E0 ; max num entries in root directory +wTotalSectorsSmall dw 0x0B40 ; +bMediaDescriptor db 0xF0 ; +wSectorsPerFAT dw 0x0009 ; length of single FAT copy in sectors +wSectorsPerTrack dw 0x0012 ; sectors per track +wNumHeads dw 0x0002 ; num heads +dwHiddenSectors dd 0x00000000 ; num hidden sectors +dwTotalSectorsLarge dd 0x00000000 ; +bDriveNumber db 0x00 ; 0x00=A: 0x01=B: 0x80=C: ... +bFlags db 0x00 ; +bSignature db 0x29 ; +dwVolumeID dd 0xFFFFFFFF ; +szVolumeLabel db "SCOTT BOOT", 0 ; 11 bytes, null not required +szSystemID db "FAT12 " ; + +AROUND_DATA: +;;; setup segment registers, this code is located at 7C00:0000 + mov dx, BOOT0SEG ; 7C00 + mov ds, dx + mov es, dx + +;;; create a temporary stack at the top of segment 7C00 + mov ss, dx + mov sp, 0xFFFF ; top of this segment + +LOAD_ROOT_DIRECTORY: +;;; +;;; Compute the length (in number of disk sectors) of the floppy's +;;; root directory information: +;;; +;;; wNumRootDirectorySectors = sizeof(directory_entry) * +;;; wMaxRootEntries / wBytesPerSector +;;; +;;; Store this in cx and in the wNumRootDirectorySectors identifier. +;;; + mov ax, 0x0020 ; sizeof(directory_entry) + mul WORD [wMaxRootEntries] + div WORD [wBytesPerSector] + mov cx, ax ; count of num sectors to read for dir + +;;; +;;; Compute the starting location of the root directory (sector number): +;;; +;;; wRootDirectoryStartSector = wReservedSectors + +;;; (bTotalFATs * wSectorsPerFAT) +;;; + mov al, BYTE [bTotalFATs] + mul WORD [wSectorsPerFAT] + add ax, WORD [wReservedSectors] + +;;; +;;; Compute the first sector after the root directory -- this is the +;;; sector where real data starts on the floppy. +;;; + mov WORD [wDriveDataStartSector], ax + add WORD [wDriveDataStartSector], cx + +;;; +;;; Read the floppy's root directory into memory at BOOT0SEG:0200 by +;;; calling ReadSectors. This function reads cx count (wNumRootDirSectors) +;;; starting at sector ax (wRootDirStartSector) into memory at es:bx +;;; + mov bx, 0x0200 + call ReadSectors + +SEARCH_ROOT_DIRECTORY: +;;; +;;; Search the root directory for the stage two loader and the kernel. +;;; + mov cx, WORD [wMaxRootEntries] + mov di, 0x0200 ; offset of first directory entry +.LOOP: + push cx ; save current entry count + mov cx, 0x000B ; 11 char name + mov si, szStage2Image ; name we are seeking + push di ; save current entry ptr + rep cmpsb ; test for match + pop di ; restore entry ptr + jne .TRY_KERN ; match? if not see if it matches the kernel + mov dx, WORD [di+1Ah] ; match. save the starting cluster + mov WORD [wStage2Cluster], dx +.TRY_KERN: + mov cx, 0x000B ; 11 char name + mov si, szKernelImage ; name we are seeking + push di ; save current entry ptr + rep cmpsb ; test for match + pop di ; restore entry ptr + jne .NEXT ; match? if not next entry + mov dx, WORD [di+1Ah] ; match. save the starting cluster + mov WORD [wKernelCluster], dx +.NEXT: + pop cx ; cx = entry count again + add di, 0x0020 ; next directory entry (+32 bytes later) + loop .LOOP ; cx -= 1, jmp .LOOP if cx != 0 + +.DONE: +;;; +;;; Did we find both the kernel and the second stage boot loader? If not +;;; give them a "missing operating system" message. +;;; + mov ax, [wStage2Cluster] + test ax, 0xFFFF + jz .NOOS + mov ax, [wKernelCluster] + test ax, 0xFFFF + jnz LOAD_FAT +.NOOS: + mov si, szNoOperatingSystem + call Write +FAIL: mov ah, 0x00 ; wait for a keystroke and reboot + int 0x16 + int 0x19 + +LOAD_FAT: +;;; +;;; Prepare the load the FAT into memory. Compute the size of the FAT (in +;;; number of sectors) and store in cx for a call to ReadSectors. +;;; + xor ax, ax + mov al, BYTE [bTotalFATs] + mul WORD [wSectorsPerFAT] + mov cx, ax + +;;; +;;; Compute location of FAT and store in ax +;;; + mov ax, WORD [wReservedSectors] + +;;; +;;; Read the FAT into memory at BOOT0SEG:0200. This overwrites the root +;;; directory information but we already have the cluster number for the +;;; stage two boot loader and the kernel. +;;; + mov bx, 0x0200 + call ReadSectors + +;;; +;;; Prepare to read the stage two boot loader into memory at +;;; BOOT1SEG:BOOT1OFF +;;; + ;; point es:bx at where we want the stage two loader read into memory + mov ax, BOOT1SEG + mov es, ax ; destination for image + mov bx, BOOT1OFF ; destination for image + + ;; use the starting cluster we found by reading the directory + mov ax, WORD [wStage2Cluster] + mov WORD [wCluster], ax + + ;; call LOAD_IMAGE to read in the file + call LOAD_IMAGE + +;;; +;;; Prepare to read the kernel into memory at KERNSEG:KERNOFF +;;; + ;; point es:bx at where we want the kernel read into memory + mov ax, KERNSEG + mov es, ax + mov bx, KERNOFF + + ;; use the starting cluster we found by reading the directory + mov ax, WORD [wKernelCluster] + mov WORD [wCluster], ax + + ;; call LOAD_IMAGE to read in the file + call LOAD_IMAGE + +DONE: +;;; +;;; We're done, transfer control to the second stage loader which we +;;; put at BOOT1SEG:BOOT1OFF. +;;; + push WORD BOOT1SEG + push WORD BOOT1OFF + retf + +;; ************************************************************************* +;; PROCEDURE ClusterLBA +;; convert FAT cluster into LBA addressing scheme +;; LBA = (cluster - 2) * sectors per cluster +;; ************************************************************************* +ClusterLBA: + sub ax, 0x0002 ; cluster number is zero-based + xor cx, cx + mov cl, BYTE [bSectorsPerCluster]; convert byte to word + mul cx + add ax, WORD [wDriveDataStartSector]; base data sector + ret + +;;; ************************************************************************* +;;; PROCEDURE LBACHS +;;; +;;; convert ax LBA addressing scheme to CHS addressing scheme using these +;;; formulas: +;;; +;;; absolute sector = (logical sector / sectors per track) + 1 +;;; absolute head = (logical sector / sectors per track) MOD number of heads +;;; absolute track = logical sector / (sectors per track * number of heads) +;;; ************************************************************************* +LBACHS: + xor dx, dx + div WORD [wSectorsPerTrack] + inc dl ; adjust for sector 0 + mov BYTE [bAbsoluteSector], dl + xor dx, dx ; prepare dx:ax for operation + div WORD [wNumHeads] + mov BYTE [bAbsoluteHead], dl + mov BYTE [bAbsoluteTrack], al + ret + +;;; ************************************************************************* +;;; PROCEDURE ReadSectors +;;; +;;; reads disk sectors ax..ax+cx from the disk into memory at es:bx +;;; +;;; ************************************************************************* +ReadSectors: +.MAIN + mov di, 5 ; five retries for disk errors +.SECTORLOOP + push ax + push bx + push cx + call LBACHS ; convert LBA sector in ax into absolute CHT + mov ah, 0x02 ; BIOS read sector + mov al, 0x01 ; read one sector + mov ch, BYTE [bAbsoluteTrack] + mov cl, BYTE [bAbsoluteSector] + mov dh, BYTE [bAbsoluteHead] + mov dl, BYTE [bDriveNumber] + int 0x13 + jnc .SUCCESS ; CF=1 on read error + + ;; read error, reset the disk and try again + xor ax, ax ; BIOS reset disk + int 0x13 + dec di ; error counter -= 1 + pop cx ; restore registers + pop bx + pop ax + jnz .SECTORLOOP ; if error counter != 0, try to read again + + ;; give up + mov si, szReadError + call Write + jmp FAIL + + ;; sector read succeeded +.SUCCESS + pop cx ; restore registers + pop bx + pop ax + inc ax ; next sector + + ;;; bx += wBytesPerSector + add bx, WORD [wBytesPerSector] + loop .MAIN ; cx -= 1, jmp .MAIN if cx != 0 + ret + +;;; ************************************************************************* +;;; PROCEDURE Write +;;; +;;; display ASCIIZ string at ds:si via BIOS +;;; ************************************************************************* +Write: + cld ; make sure direction flag is correct + lodsb ; load char to al, increment si + or al, al ; see if it's null + jz .DONE ; if so, we're finished + mov ah, 0x0E ; service 0x0E: output one char + mov bh, 0x00 ; display page 0 + mov bl, 0x07 ; char text attrib (white on black) + int 0x10 ; invoke bios + jmp Write ; next char +.DONE: + ret + +LOAD_IMAGE: + mov ax, WORD [wCluster] ; cluster to read + call ClusterLBA ; convert cluster to LBA + xor cx, cx + mov cl, BYTE [bSectorsPerCluster] + call ReadSectors ; this increments bx + + ;; compute next cluster + mov ax, WORD [wCluster] ; identify current cluster + mov cx, ax ; copy current cluster + mov dx, ax ; copy current cluster + shr dx, 0x0001 ; dx = wCluster / 2 + add cx, dx ; cx = wCluster + (wCluster / 2) + push bx + mov bx, 0x0200 ; location of FAT in memory + add bx, cx ; index into FAT + mov dx, WORD [bx] ; read two bytes from FAT + pop bx + test ax, 0x0001 ; is the cluster even or odd? + jnz .ODD +.EVEN: + and dx, 0x0FFF ; take low twelve bits + jmp .DONE +.ODD: + shr dx, 0x0004 ; take high twelve bits +.DONE: + mov WORD [wCluster], dx ; store new cluster + cmp dx, 0x0FF0 ; test for end of file + jb LOAD_IMAGE ; if not, go get the next one + ret + +wDriveDataStartSector dw 0x0000 +szStage2Image db "BOOT COM", 0 +wStage2Cluster dw 0x0000 +szKernelImage db "KERNEL BIN", 0 +wKernelCluster dw 0x0000 +szNoOperatingSystem db "Missing OS File", 13, 10, 0 +szReadError db "Disk read error", 13, 10, 0 +wCluster dw 0x0000 +bAbsoluteSector db 0x00 +bAbsoluteHead db 0x00 +bAbsoluteTrack db 0x00 +szProgress db ".", 0 + + TIMES 510-($-$$) DB 0 ; 0 padding + DW 0xAA55 ; bootsector signature diff --git a/boot0/boot0.bin b/boot0/boot0.bin new file mode 100644 index 0000000000000000000000000000000000000000..764f7a6d00d2a53c20a26c6d30939b99b27cf380 GIT binary patch literal 512 zcmYk3!D|yi6vn@|(WW+bn;KZP2bqHi9xSYfT)foQ7DU^%Zi<#3ieRuoQP?zhbL(y( z2mb=sf>7x}aM5Tb-Ru@b%0ilx2!aL=n|cWdO&i2DJHZi-mY;Zp2hPiqO{os7~|64-CzQ?a>i-V>}~+!-s&EOPXtIFf_e`Wu>@KIwl7Ff zKU(r2I^SY_4*6=MlRkyt-ioqM(RlEyy1p%5=N`A{o{+U^lk*`s2&J!EY8K(qw${cJ*;7E8J|d2?p+ uI%DHkXM~&1<@33R_n4-$soaC?-~g`V^7omMy>pjk4dam^NKd1{+>1YuX}+ZZ literal 0 HcmV?d00001 diff --git a/boot1/Makefile b/boot1/Makefile new file mode 100755 index 0000000..5606866 --- /dev/null +++ b/boot1/Makefile @@ -0,0 +1,14 @@ +NASM= nasm +NASMFLAGS= -f bin -i../inc +RM= /bin/rm +TARGET= boot.com +SOURCES= boot1.asm +SUPPORT= video.asm hardware.asm + +all: $(SOURCES) $(SUPPORT) + $(NASM) $(NASMFLAGS) $(SOURCES) -o $(TARGET) ; \ + mcopy -o $(TARGET) Q:\ + +clean: + $(RM) -f *.log *.core *~ *.gmon \#*\# $(TARGET) ; \ + mdel Q:\$(TARGET) diff --git a/boot1/boot.com b/boot1/boot.com new file mode 100644 index 0000000000000000000000000000000000000000..663284c95846408aa404a604895b30dc97bb0954 GIT binary patch literal 310 zcmZvVKTE?<6vfYLT3f25FM=WcXKl06^7NecBZNa+ViH-+jTDM~0E9oyC6fq@2e zh}=blN9!Wpbg?|!MOQ`YOI9EM3aP>)b2P-jq=_$sm0bClR}1FDUf z>Gq>Kb~#W$HRhNRub~PDcUa?SzQq;2+p3jy9Rp0CwI)CKPt&PRulksIyN?yrH1sDo zkydvXYM3$tr?s$$y8Ho)20sv!gC;$Ke literal 0 HcmV?d00001 diff --git a/boot1/boot1.asm b/boot1/boot1.asm new file mode 100644 index 0000000..59c0dd0 --- /dev/null +++ b/boot1/boot1.asm @@ -0,0 +1,113 @@ +[BITS 16] +[ORG 0] + +START: + ;; initialize segments + mov ax, BOOT1SEG + mov ds, ax + mov es, ax + mov ss, ax + + ;; make sure we are running on a 386+ + + ;; while we're still in real mode, detect system hardware via BIOS + call PROBE_VIDEO + call GET_EQUIPMENT_LIST + call GET_MEMORY_SIZE + call GET_TIME + + ;; switch the processor into protected mode, keep ints disabled + cli + lidt [IDT_Pointer] + lgdt [GDT_Pointer] + call ENABLE_A20 + mov ax, 0x01 + lmsw ax + + ;; Jump to 32 bit code, this completes the transition to protected mode + db 0x66 ; operand size override prefix + db 0xea ; jmp opcode + dd (BOOT1SEG<<4) + setup_32 ; want to jump here + dw (1<<3) ; ...in the kernel code segment + +[BITS 32] +setup_32: + ;; set up data segment registers + mov ax, (2<<3) + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ;; create an initial stack + mov esp, KERN_STACK + 4096 + mov ebp, esp + + ;; create a stack frame, pass ptr to hardware block as param + mov eax, (BOOT1SEG<<4)+BIOS_HARDWARE_BLOCK + push eax + push (BOOT1SEG<<4)+.HANG + jmp (1<<3):0x00010000 ; entry point of kernel + +.HANG: + nop + jmp .HANG + + +; Setup data +; ---------------------------------------------------------------------- + +BIOS_HARDWARE_BLOCK: +bDisplayType db 0x00 +bFontSize db 0x00 +wEquipmentBitvector dw 0x0000 +wMemorySize dw 0x0000 +wSystemClock dw 0x0000 + +NUM_GDT_ENTRIES equ 3 ; number of entries in GDT +GDT_ENTRY_SZ equ 8 ; size of a single GDT entry + +align 8, db 0 +GDT: + ; Descriptor 0 is not used + dw 0 + dw 0 + dw 0 + dw 0 + + ; Descriptor 1: kernel code segment + dw 0xFFFF ; bytes 0 and 1 of segment size + dw 0x0000 ; bytes 0 and 1 of segment base address + db 0x00 ; byte 2 of segment base address + db 0x9A ; present, DPL=0, non-system, code, non-conforming, + ; readable, not accessed + db 0xCF ; granularity=page, 32 bit code, upper nibble of size + db 0x00 ; byte 3 of segment base address + + ; Descriptor 2: kernel data and stack segment + ; NOTE: what Intel calls an "expand-up" segment + ; actually means that the stack will grow DOWN, + ; towards lower memory. So, we can use this descriptor + ; for both data and stack references. + dw 0xFFFF ; bytes 0 and 1 of segment size + dw 0x0000 ; bytes 0 and 1 of segment base address + db 0x00 ; byte 2 of segment base address + db 0x92 ; present, DPL=0, non-system, data, expand-up, + ; writable, not accessed + db 0xCF ; granularity=page, big, upper nibble of size + db 0x00 ; byte 3 of segment base address + +GDT_Pointer: + dw NUM_GDT_ENTRIES*GDT_ENTRY_SZ ; limit + dd (BOOT1SEG<<4) + GDT ; base address + +IDT_Pointer: + dw 0 + dd 00 + +%include "defs.asm" +%include "hardware.asm" +%include "video.asm" + + \ No newline at end of file diff --git a/boot1/hardware.asm b/boot1/hardware.asm new file mode 100644 index 0000000..b7ac3af --- /dev/null +++ b/boot1/hardware.asm @@ -0,0 +1,42 @@ +[BITS 16] +;;; Call BIOS to populate equipment bitv in the HARDWARE buffer. +GET_EQUIPMENT_LIST: + xor ax, ax + int 0x11 + mov [wEquipmentBitvector], ax + ret + +;;; Call BIOS to get the system memory size. +GET_MEMORY_SIZE: + xor ax, ax + mov ah, 0x88 + int 0x15 + add ax, 1024 + mov [wMemorySize], ax + ret + +GET_TIME: + xor ax, ax + int 0x1a + push es + push si + mov es, cx + mov si, dx + mov cx, WORD [es:si] + mov [wSystemClock], cx + pop si + pop es + ret + +; Enable the A20 address line, so we can correctly address +; memory above 1MB. +ENABLE_A20: + mov al, 0xD1 + out 0x64, al + nop + jmp .here +.here: mov al, 0xDF + out 0x60, al + nop + jmp .there +.there: ret diff --git a/boot1/memory.asm b/boot1/memory.asm new file mode 100644 index 0000000..d834825 --- /dev/null +++ b/boot1/memory.asm @@ -0,0 +1,3 @@ +;;; +SIZE_MEMORY: + \ No newline at end of file diff --git a/boot1/video.asm b/boot1/video.asm new file mode 100644 index 0000000..61ff153 --- /dev/null +++ b/boot1/video.asm @@ -0,0 +1,81 @@ +;;; Display the ASCIIZ string at ds:si via BIOS VIDEO call (real mode only) +Write: + lodsb ; load char to al, increment si + or al, al ; see if it's null + jz .DONE ; if so, we're finished + mov ah, 0x0E ; service 0x0E: output one char + mov bh, 0x00 ; display page 0 + mov bl, 0x07 ; char text attrib (white on black) + int 0x10 ; invoke bios + jmp Write ; next char +.DONE: + ret + +;;; Probe the video system of the machine via BIOS VIDEO calls, populate +;;; the HARDWARE_INFO buffer (real mode only). +PROBE_VIDEO: + ;; identify the display type + call DISP_ID + mov [bDisplayType], al + + ;; determine the font size + call FONT_SIZE + mov [bFontSize], al + ret + +;;; Determine the font size (width in pixels) of the current display. +FONT_SIZE: + cmp al, 0x0a + jl .TRY_OLD + mov al, BYTE 16 + ret +.TRY_OLD: + cmp al, BYTE 1 + jne .TRY_CGA + mov al, BYTE 14 + ret +.TRY_CGA: + cmp al, BYTE 2 + jne .TRY_VGA + mov al, BYTE 8 + ret +.TRY_VGA: + mov ah, 0x11 + mov al, 0x30 + mov bh, 0 + int 0x10 + mov al, cl + ret + +;;; Determine the video type via BIOS VIDEO call +DISP_ID: + xor ax, ax + mov ah, 0x1a + int 0x10 + cmp al, 0x1a + jne .TRY_EGA + mov al, bl + ret +.TRY_EGA: + mov ah, 0x12 + mov bx, 0x10 + int 0x10 + cmp bx, 0x10 + je .OLD_BOARDS + cmp bh, 0 + je .EGA_COLOR + mov al, 5 + ret +.EGA_COLOR: + mov al, 4 + ret +.OLD_BOARDS: + int 0x11 + and al, 0x30 + cmp al, 0x30 + jne .CGA + mov al, 1 + ret +.CGA: + mov al, 2 + ret diff --git a/floppy.img b/floppy.img new file mode 100644 index 0000000000000000000000000000000000000000..fb06de44f0a9475e8bbe44f6113d75b58a48bf26 GIT binary patch literal 99840 zcmeFa3w%_?**|_Z*+5tV8w3;(b*W&3O2{Sw5-v3%EJ7g=2mvqEu-R-#AY{W{NT?#g zg_@H!Hnz6f*0vzM*w(i~YlT*;3E?GpZ4kAn?OU+cp18aULLo|I|KDfMWzJp>*uMR} zpWo;I+l41*o|$F^fFIl=`#nN&?NHLj&lw(5C(33-i%Y+nx|0b{8 zc6k1hrM4x-r4Ac@^NUy5b8-D$=imq4eqjFtuRR-y?6V2yt{Wkon?6(!yLMy(xm-vc zG+fyAl*u&wDbq7TinLD%4Id#qFQhE{?NH&lr%kI)|D-Z7I8-rCynMfjq7LYzdQG=( z?Xd~(zP59}>1UCMKYeE}T>}}@TzpA10hTo;v6$Q@8!KbJNEN7xuSa-fyDt zIHC3OPfU+}YU+MIO(-j0CjB|`RLX8sTlS}>w&DGzw&|ak+Ag0cyfV@^(%$*X$fdX5 zeoNr!bMKj?;X+?!1Xxk()kB4M_jeCGk>2UIJT-i;@aw^^WQI?6?}T105m~sjGXhlX z689hez;w7AWiY)Iy5jbqnS{XLt?x#xeW^m%2WIi|v!<<|p0s)obPK}VrAt>(J(Mgh zMI|kAEMMYSY_rW>xCB^fozGX-u*SBu+%~_i-aX7B%&YUQvw7XlYMa~Z^>`7(e%XRh z@$-|BNMxyywptk6A`ICf4DA$#9TTjAX@t!*vdA=gwdsl$)7TxRD?3e*k4)DHDHCid z*A}HrTAlL6mXygmQf}x>nQ|;;s*oD7%1o1glNLx?AZdZ51(Fs>S|DkGqy>@|NLnCi zfuseJ7D!qkX@R5#{_nCta{cH3uKGx7le9q60!a%bEs(T8(gH~fBrTA%K+*z93nVR& zv_R4VNeleDEl^w~c<|3JWaF)$4_pYYhX}kB6p{bx-ZZj}5gz^OEmLoqDzIycFx?~w z#p?vO;1wG1*DAbS4>nao9Z>oa7u~x_pzps+rl)JF@N!-Z{gp(&UT_MHarC9dWij+u z2@}7_vL*i{Es(T8(gH~fBrTA%K+*z93nVR&v_R4VNed(`khDP30!a%bEs(T8(gH~f zBrTA%K+*z93nVR&w18p(+AHDc1Dy}_N-ql1Wk6oho4W?!bjD2_cKJ<$8CV9ON5zwNsnX=Syy;O_^5U_!Qz@Mj5aOwSdC@BRVL z266qyAA~VNEr{eF_D~qH>kgsY-uYDPE2Gl{^R7xlUKuxNY*Qe0_wer7DY&F} zKQ&@^&R*e_%+^CBZrm%Gv&RKeTaR`>b=fnSh~9cWQ}7RMi3tAG-D%ys&E2nzn3yv= z2c)L%-6{VbCc-dFme1|qV5@f5I0No3ON^Ei#r@zkAVDoH5 zqQAD!2itd_KNBA4=%^6?7CbSg$1(77o=NC&^b18MA$VXQ(gnJ zr+t)QeR>!%7kjNwcGXy)9!(6|GcFVNn_jFDD=t{~IxZlVBqJx$Sf4zM^!8;J3VXM3 zUDoMj_+=Snve#1uL8>3j4Bpz=>iCRmXSf_aO^!wk390Fl(ydSTF0XxqNXE)((s=9* zLL^q1Bt?l%lcTLqJ1&&T1d>uM;USCwDW8?eG_NWthmoSRkuj*=P(?UJ);8epT3Ziy9yhn{Pk*s>bS6rywU047(}9YP0a%2Y&wQC;oAxh9U_Ukb z%aQg`L)tS^#L_cC$C;F_^=DG3`Qit=6UQNrekCOC?|L_LNJW2e^BEzK5*~$?E%t^g z`o+wkW566KJrn+0$H6qJ6yabRRg@t0eHMxEwvnakw1YJ2m0&}$qCb3<+(v3UAylrK z`P59_8er8Q9*1^O+ZCPmdpWJ6$8iP;f_N4xQoXmAT_~(L<4p_S1fD&Pla$yIa-5vy z==Yk#Ul7lt7eeFqjBim23J}V$i0Mpy$I0-U;@ODfO!(J~_5|h7Y;Ypl20#;9H8h&6 zZrU$a3@ECBSIBWjmVdb-|Ab;O$oebNd7YuIQPB{Uput0;(%wynxER!pBNFVl_*23; z7nsp?{m(l>+J^SMgDi95&|_33O8tbt!u0BItBAB_M&P6z{cWuMFzf=WL|y;%PEqLM zMU0dtKtvnsbr;YGNyrP`+#iw^nXL4d0wG`y&!m1%KFj)a=5i@bb_Eefdc-lXb@PCL zMze7w^~MTn0#D1y;k1tMbK+U)d*`V&DZKUl0{ubuI9`iVSeUWRabYGiBqGV47Y?;L zE+8kf0}>x2b4oXBJi)Myso$CjC)~!Y-3nLD<+WwDRzwauE~JDip#EG_uz$G!YW&$U zB-o$s&!F}d?9cR%43F(VE+KrGcotVv*ut(U;S}*~%jU?iruo*VDG3ZagxznKK`w`V3#z(UV4vO=ua4TBWG5 z2U&5;cJ&U5j&q^m)5tVUc0g4H)(C)|(9j~+YNcEE5w+MSyv3eS4%WU~{l7CR& zRfy?8wV;=@rOftOw><%2vwd5#X1lE0Iw|bhlDVzZpEG-sKfmvfhyeZUYX9uMmy~e8 zy6sUEZg#VEn;B7kzn0_sj>%V4^V&&L3S0!V?E5()VL!*~=8)s{VCO(6Q>^G+Ygub+ zcO1ot*jpw&3WbnU>=YgStPS;J+*9L%{Ue)FWM6SVILUsbmKxE6{Y-#LQKiV6nLysl zT;9uN(v2!_ojeItOb4C3Z$!(R5H0Ux*;Vzu2usA4@8z|bYWX1VS(Ud=Ub7mfllMrp zyglcl%6G5gHt*<&w&fvafJ0@HQ^_zpI=vU^ZI0fFa6i4vrFyyTgs(uIGD3_a=&uz@ zv=D|b)5XtGx5}Jm`oB=)Lc^MAM-yE8`XXeE%#*rY{4%m$hl?Z{cy&#%Tm?v$Vp=;C}}E zUHAxV>y*zwEVvmH*1^cN;2;|lAXw*_X~j`_RhZBP_cBe-#gl zFLi}e#kabSr9m9PRMZrMqx^XD$Wq`Fq!Pcx#MDQmy`+}NO6BU z8}kQ+h43KQ^xJ@qicInkXM*oqf7=XsZKEt0Ko~zvB+QI-2qPT~8!_5fL5nef>FXG1 zt2op0?qH^ist5>E6UclGbj*LAHU);Wbl7O%pfDwzD*GtZ8qz~|9F1xpG;w0K>_>Y= zJfrTTfZqd z<>@=laX25L?=Z(P)fD==IBq6<563Yt7y5Q`98*c5??H}ZIx6&SWq3LTH&T%OWPLE@ z6#ASjo%HTo$o`7~@XI;Q$_rHJGf|{0k8WN2P9bYY?Fb6K zPeGkNmr>+lmd@zOs(oD?A4T{cj*ljMC&w=*{6UUiLHJgVk0E>m$Hx-x#(o<;Zuj!!1s$?@w6U&iqp2ruS1HUB<4$EOfJf#cbPkKp)J z!c82{A^a4Yl2U#<;m0|iOZZ`qR*n}CzJcR~ zggZGtlkjC6pG9~v$7d66=lC4LCvcn`M&AgI-$b~H<3)s@Lf2BtUrhLMj?X3hFvm*> z@8bA8!uN38LHJIN&nNsrj*}%cs1dBIPNBVC&z0De~{yA2;a(a8i4vXaJ-IiC&$+kzKrAR2ruS%J>hna zuP1y0#~TPA!Eq1aCXP1}ehNd8Qoap@ALqE2@WUMU5#GgdKjC{g9w2-t$2SuGAjg{s z-^%f3!Z&by6X8ydf0giM9KVb3VvgTUxSivh37^36dk7!F@hya#INn0|DU8ZW`L_~& zoa6TrewgDy!n-)Wjqp7j-%j{Wj(?5t2RVKp;afT0O85qji-bEl-bVN`j)w>@=J@@D z+d1A&_ymqWK==rbf1Pj>$2$l=HApM}gM=UF_%{eY%<*p$-o^2U2;al;ZxOze6w^_(8%S_#X&2alDuCQz=^c4-{6UWYo$#$3mk8g$ z@jk+x9RGmuWgHI^Ud-_mgxfj(--J)#_=kj#;P^*`n>c=w@KgAsjDHmDJI?Ws38zM( zq@O0di{obq-^1~L5WbV+X9<6h2ebcEex3a#h9EgRDEOkL#Zahx`+7+$YS&y3P3AD9)PzHR^Xws zAjo0s&#XHKUvo$0ows3w{H+Jlk?;rQ*<*SnZ8z3{gLCLF9@mD)bkOs(akU*-m#O_e zErhoIl`RyW*Vfe1Bf~h2pwUc{Y4#A!3Yq3pg+{lwLeDp_^o)la2ZcQREjlRJu+kE2 zzHzGcYczPs$W7M!Xk-KuD!Vbb9}j#6)MtIGWH|>(&WU&BMa<0u)+SZ=TXg9!%js>7 zfr*1#_qV*by0zkbaPz?6COjRYXCUXx*4m`6hZz&;8z17yjWRAsYrzE^x<1IP5z7W~`=xK)oK8)mDNTF+MRt;l{AV)B zTgO>xKrilZH`DY+_#JB;r5R#|_FN(h>yryIOv2Xn0|mH9H~n`cqAZB^I3$Yd*t)r& zByCI$V@-H-e|EsK4{lfpr(w;|j>YsdXecN=7TG00KS?8JDztY9`6gkxbV7Yb+FyHe zJS{3>-Ms*7GFS&^kB$aR4k`Ry@hpi2K~0b2gpiJ0SknCvmo5oKcbvp(al7M0ne^=w za!nuTuf6*kldv{N&%XeVnAh5+x4A^>W@{%%PxZ-Fo5>z1YR|xFaTbjbm0H_E8J$u{ z&KOC17X83#$I%eOjv*xW?s^ZGofL}|Sv1}wrB$fZ{&Vg#SRj3|>w`4$2rZGOO+HNP zq=(x_wSyrQc5?6b&Oph9v`%99G`sxnDTG^QV>xl~K^B3018Fql!&5rS_gl)qRzoS_ zhe)8_5%rv0+Qio3&#bMM&Pl9r^w2pXy@7^_b?%8KJX)Cyqpe*n{gJ0%kbc7&ZvR?S zZ9lb=FTO7u<{W#-BQKySe^L6;^iY2nJ=4d+Bb7)0D=sg0EMKv3=@Pu$V4LK#IsNpv zzOKP-n-s{l2n!qhIP@aW==UK*oxjdmU-wmaH4;|Tt#|wU&h?G95>J3K%>==ufCbEH zsZNzqT5@x-t*H+0WH=j~^`130m#5z26?pV|PlLx*>-DU6>msY`*3|i(^-AUvdT*m} zrmX_+a@0VxYGI|ylyS3F*ZCUjott_5gayv}m6TBK_AhXHtDBr&cS*qOgE_Pmm)BEYFD$QE zQoO48*76n07cRMZ!cDZGpynv^);0KH+#GvuUjDS{GYSfe=a$TK%qM%l^76|sBUPy{ zS)ilASyk`mwMdL`o=JS9mZjX?P`%K*!QrW+yjt|a2Gn+a{Q_r0bv+8X0+mmvq)ci? zTS)$r7D!qkX@R5#k`_o>AZdZ51(Fs>S|DkGqy>@|NLt|kDGShw{z$Az3Bpie?AWnF zKRpdl!e=Oql{$)lR~P@15`P2Yf1|`xc$Y3uyAsa5a;&gX$xq>GU7p2CJn^5Qi?`|G zsWR@KI^`>Ljs0O^W3k86Xt7;iQ&(Sqy-oe)H0R{x*a1!+lWWV(&z+W6Xeq9*_qgy$ z5Y|Fmp7mHzviUZxukzIUEG2qWq4Z((Ol2ZpAt)T>I~y&Q;_7Oz+vl@+YHU8Yi+zoi?RPi(f%$EIk1eN}Dl5mbcxmxG zwrV*QpMZHcWqWin>z#ECY6O{8UZYcTFgttHWUs$|st-kJSfh`T6;flAn(+l18$Dis zOsu*fr{nI&>ZwXIXy%f2oA&`xaap*bJ4`u~}s9Iv$lwh}T^YZR`Aufr=0< z!p^7^8B>zv_9B}p-fW1`__2mX+!#sr^)?nJJ+Mf=&ePy>XZy3S*CbMQmDVXskyW7t zh1$tXlYl%MtJ`%dHA6JM`V^-=DckF*cKV$fm5j<-%Hf1Mdv!WTb8spg84DFjtT|_r z9i;;yvIZ1ND8E`KUr*~~oD|tQ>KL1G@@$vij2!LFwf2WD~ro%&dsTkdyCvteEvX{P3tT1{X|YP%<5j^HW=r+ps1UYR{yN8g5~(IvdJqo>m}Fp=oLtBZ?+ZG|4Z_SF>vw>I^HH z^VTwZ0)}0+aC%%Z>hNS653UV$pP`rxUN$$!7BvyoBq{+#f2Z0{dHQlcJ;hz&DfYSQ zFuREPspVEh@np<-8(fWsKBUkc*DgU(PhCPky8=H&L{*{M0CJl1@>yxLNi>%#hvG=I z3@8nnoP48VD5+Xjjcx?0rmMG2d~&WaQv$LyyDKlQg3!vfJFJ`C4Q_9pOS#c9GGtZt zMVashTeh$S6N8waT5hEPx>UU;i#^V2+%GOSBELjIloFJA-Olxi?iLj#T)8zm`7B6E zc;?AXHYQCa%D-swV?-C%;BAF}PbucQYdl^I_X+Ra?XCiCxJO@7?%gjkc7s4E2eQ}5 z;~l#{Vz0s&x(~anN^Rqsd0?}+7cLBnYWep8TQq7pc2~_s?ayECFjB!) zlSqYmo+TcCEpCC0$f`eYv%eW5o)Htx=$i3psE&Pw#faK{@$MV_0*is`u-9mQFsB)H zS&O9s^;0GG)_uD7NsN7GC|)K446Q>y=Me;4;gif#qP*)qZq|btsRvc*kevf zw|i67+mXZ+c1$|h4+H5mc^uUy{fQe|?hL)3MdMGXNv<}X1ml93d5r-{p<0VjUYW)< zXs^0qI%hz}-J#l!b=^$0yIp5mJGCr#ZDoxYtDcp$)$+QNKEkM}qe=x7@s(I8RR8IJ5kVkj5#ZYx3CGit4`7>Ge7{ zRiamVorZh+YMU6Rw%KwqN#crm3Z;z3%Etj%i}~kEwP}1F&+zW>l@WEUT%c4bEQ@mw4rt$IkXlw&}8zFI~PuA;Bsv`Xc0O%gO4Bu%5fJEPOHUJd4bo;Tm@( z)snr|B@Z*WGE*o^%veR1S816kXh~T$xO!Dvdi+{jrJHr^IkxP7f=F|X zZ&g-eX<8mnFivHoN@&qq_fwTs7Y^Wpa^w$Q@3b zwqKuBvb3~x=@Md}=)rDYo?Gn&=r?Fra&}FKqPF(*6l&uc8r>PxMa@7o0;2#J*|OLZ z;3{^rYNKaV&1^CoOA;%Sr?V%;Mpa6R#r!_+BVt)13K_@YbIv;c&7?Gxv1Sxg9>rHC zlEz!FGRLrFV|G);Ix+>QYta?UR9YOuYIHWd4^}|Y>nRh%qLTDvELBfSTZ1-pVuote z42vRHV<-!~Hh?8uwVbRXPz`EDP@9~_I4c>^j$>$INnj&;P0TYC0}|wuf4Idch`A?- zR!kL=KSNJJ-9Q`n4P;D@lEFF-TY)vmUKk}HdePH>q$)}Rk?@s80}?lv0vjm3S+_)( zn9QzC@tM8ioqz#Zt~&M6$Tat4K!N)$UB!?f-HnSu1F+BFVw6IW7&PbgjcgK-utx2L z)v=;(UCZM$1t@~0RQkNGx`rCeHq^Xoj4n$J25-C(qRS%3 z@GMTRvtdnC7A1yfX$(}wvQeXWrWjQSjk#NHs*@kL%Gx`ts=V%vI?dz=&QL40oIkqk z?0!*4%y3St_I|}sve!}gc$B#e*|`#L6LG5b&RBygF+59?+qo{*7Frz7RugD&#b(pu zcsBp0Mt5vBEskf~h((x~YE+{pTb5R=D63eJg%^165H{P7F9hB7HF>#H>hcPv6N?6q zUu~w;?o_{``a48a;m#x`TY_A83`sX?d<`R-u}Xn#@()fi=E0?b3^>9ViaOa}B^IDP zvtCw7{>q8v|R?X-nIjY#K0 z^wq7_%0&rIw!5X<@8sV}XRKOT9@?C4eg5W*$J6~~p{C63XZ;I9*GxVw&UidEa7F0m zQR2<%;XHiH+_E&INZytDhK`<_>En1Id?n)Xd2{*7NYnVx%{NA{%Vp;F&U;>;+-*9z z{hclE**lrMO6{9?Chv~)P#{y;_z~MV7CHttPs85r*l4k5^0|&s>40_bs72zbBk!`{ zyhSPECrA2+><3mOj#^YKmg0NrjKZ$F-@upO*1hI(WIa|Rn#*s$6N2RZWb3J1B!@PO z43&-&dxsnf*h9mP{mLZ#j^3LTd+o2CJB9C;OJ5gDj|Ml75wHU*zS|B}j1k9)UUT0M zX)le!Q%!>cUnqQgU@v?Zw z>tf(2zSPDp3L(c)_KEh*DG1ewH`D&Bas+l*d>s+#Vg;!XsyK?T*h8hgHR3WkYq6HK zSk79U-s5x{+e|=?cftBeKxrHSyP}XRMBhQ>or^V-kgGRoy8a7yRn-zQ1cKf zC`J5Iju<3Ih}|-puAp74fY{t~jF{O|L_%N+nTO+-L$KrWQSog?(M8DXeJ$8(gZ5kP z#csX5HNmf@z^3(KLp{G- z(__9>5EgAZqfm8*Dqe0MHCL>7xyQjw40BlbZb;dBA+iPE@(;&;x%*6KWo+Z=;O2e8 zJvXwi>-XWy`p#6xvt_HKZ?O$%v2EtQ`W<>HY2{r@f0f?Z>e?fyrn7Dz5b=cCKLZW8 zr2v1ozq2_roGYFc-@YL;RFn}icZ9FAzc9l@GQu$uC%(n&7pJ{neKAd25 z%ahm*;g7q8f#1@1`yc;OJan#iLD}-PDbk^_w9oNCX!MGMMp=H$ve=MI{7Wp0a}Op^*hAU4NCS4B&x#(+7;6|leu%+uK|0CLw@`2h!xwmyf2)LM7sW( zVLHw9FCEFmVnd0kBD`+K4Qc5pU3*zX9GyXizG^Z4&Pp4Fzur-xST0=w^C0_Rdnenk zLrge62GYCUPobWe(cYL6nnUDWe@{W-=yZv-k=hiDU29X3;aCPvqL9v$sk>qH;Q5Gu z&Q6y77-cVuOv?mqxs-++UGH1OX_U5;;>4@yFB;jZGU?j!*s+}IEu0cJetrexT90El z60zm+ZXqyT8ha%g`WRB?ruNO9t7;!Ytx9JQDISzQ8;1+hFsNG}*b0^h&0iA4^WiUm z))crL7O4F(W&dortZ`;I9scjCLp>(+8tb;DplF!|$37o{wp8;0>vNsK{x4bY8v;Bs zal3%6xy7?P2Qa3ZI(N~fZ!}U+XNRXSY%nnFqt2QbG-f+1D>zbwKH4-1xbSPjfd7%> zES)yhLV7+-L%+Vg9B1i2mGOCM`okP&>G#R_0wq2C)t)p8Q(Cy8CvCPGUnPfG{K}rR z0yX~2a+t+`sVA*Sjn9?CEdILCG%6o2C0VtTton8AKq9h4)If=K`yRN_h+{WwpJILB zOXO4MFwOkeDdUnd=wXnDg47t+X0$t=DwBT6I$UOO4pYEy=}Ds!ll0&mR(6UaBeAHM zW?3sx>l7q6F1zGLW`p+%pqJwu6Vmf0d3SN($o9c#K}e&%h1MSIJhO{BP!H=d0!DUn z4s)n#g#bjdiiZ|mlPE+kWbG*FR?LTXm4PafCg5+o`w`}yb-qWP`Q4GewVU^Zc}=@ z`5Lh}ee$iDUEz%Fk-%lpq+B|HVu?M}_#^&R;)SjcGj5nhrD6T8$GjU87~!{MvBI}} zv=4jxD7%u>fg%XSg>QLJsP?^rJlRp|-?hC+TP3|TOg0A2RKWh>bEv$S>R>k-R&=Hp zlmL!B^kFx#Uv+4T9mdoA6x%uVTbt6@C<>#HE(_Rx+lTuDpDbU@M+8%#j>9hF!!>uNC&*lTBJ} zKC?=qIwU8Rx&58Mc%~}09r_Ox+}?@EKHBb!Y~J8;J+qOSt0Fp1bi~n}>z!bJxF)hF128;P zabj}CnfXME`3-H8q>Kik(bNgV@%PFBlq_U!6H~?UZHzqz0}Hm1r|meQn_LN-2SVfb zwJ*vH-i0lR(t}-QYL2pD>}SuKOo5@H5#m`>=lt!x{!i>CQXw!(VV@e>JRq8X*F#N8 zXfHlF`Dj;vO4qS86Q**DEJxl~TNrt87!W5DQ3DfCrk0rH^Nd)q-F;M@;{;F8SO_)d zk6u5~;mD@xfpQo&D$HZgx9d=q29P^blc zX0b7MBa1Jhc-mr4wQ2kKj^M16!0qRH$OHJTP^`9wGSR+<;@Rh@DB-~!TMtqI_Y1*U zss4|HbJ$2=-ToV7Dhy;;?|Xt+evRBmp(DfkHQM4!?C+z!0da3KD|O>tp;S!o(-&;- zVKbcC7KjKpK==MhBsVnDy7$YLBa+zF)t3f;65O9D9t^%OH$p7jG-$dvcp)<|uCE!*oorqEE6_;yorQPn3@<|25r{ARG_aC&E*vkjT(Y2Vur9zRAAZOk zSev#ICRrwZ9kVkeVOhjhq@G=2$zaWrkg z{JY3Z_%8a>w;K^CKjVob!icgoy>L;6Z&Yv=nt$j@SfH>Z!mM^s!i;A)ZQIlqteBflE^;byit=U=8i7SCj$QjNvDB z0i|LX38jds+dKW`;w|Q|qXPm0Lrp#`H>8V;(!=>A^%^En)*ZRv61#iSsEqUrk6`Wj zFH|$u)AC&cW`^5akQOS*oW8v~(|j9FEbMB!LTPh7=2lXvSZSPLY*dK0Uj#+L z4n_z80Y{WNf0h;{<~ zXKM57p6)X_^<3mC?Q+$!)MuaRh;#fkaP?337iPDZ>0VhmL$Sd*1&n?K<~_qm(=U;xvoV5@SH&pH@A*g3aso;fQ~cNECM_4g zG_Lr}POSJ}2h;F0(!X3P|5WZX%8dUS@s2avHj=alBMi!B`zS#t_#3LvHaXPKA+g4} zIYT*$Ene3wy{@;$>6)We)n7llcvT&Nitt~UC|s%5myL+o0P_2AL?XB*qv1Glg7bn zBBq)FU44H8D?G?;XfU~vXNS(T;i2f%`noCt*Hn52rhH6JkEnfae|Gte8*TRNX*PRd zVd2!Af~mPPY`J+erxni3%e6J)9bvD{(cEa82p_bnJaqTO4C~YLCT4~fPaL&sAoz*( zo{6moa35|hwqA6=i@Wa&LN2#ZOEaw>LzPN2R`KDHR z*D1=)NMMzMD651q-h#2YFfAP(aO04jbP*BJo<{pFztZBhQu3}0M6<>E?!L)XKdjx- z*{dPP_)vvyIhMpCfiaQ5D6Ba|0vT9AT`Sc7CpbtSpq(glSlQPe7?AT=pD!6+Chesg z=dSH9+&xS@^c_=4*@*HLv_4g{)s+$XNIVp^-z%d(TQbb#AGtP#mgQs?PsoF?OoBG1 zXFrdo!1_I784u`*PKAd?%gbaIb;N?LmwXxt43#;BhoSCT77fc3OUlIZ6^x5gZn~*I zaK`$45eo1sX0J;5Xw6lb9Y6m#MnJ4d*vUUsriYr$OPa4im?i=8olrTZc#+qJIN;_o zgWb0*+4NrMCiu|TFrQPG5s+WCGYXYaJJNq+5v@&InMq|`X44u291ttcburMyLM}+O}ef)k^Ib#)DQ-ib}^RwEGesfEi zv;d{$!oBU>0koid5-g!5jLKYs>!I;WaDMuHWy|UzCt4N^8G5kDW;$3jL9Ij9 zKKQ8JGH#IUwrcP43$}9UA?C(1(N_G^$SGnUXSO~}i*KV?8oV03Tz&9-)mnhe+A~zxGlMGW>MdA5GWmy6UG=A;&L-i&RUx}ud6HhtU=lOnWz|&cLZ`Q`t_A^gu>7qkowm&JKT}gXo z6O#k|<+Z&SuB5wQ5)3_9eV7u7g2x13KK$3xSOK?MxH<2yoknin+>Z@(uUJVWLm zMtT=*>JMRhbvE#~1mi2!NCOy(XD>ST!N2=-4n8|K8=vFlQB>X#N^9}-;~?U zIn;@+z2m8#>>my3X`TK^=LEAtzc(R0{ENL4_f*hIC_?|c=QKGQ)F=5BC5_4yn-)9) zxr)*g@f7}i8kRENRhFC%(@JgnwdU`t4;QK3&qyertpi5Q_%l&e(sX3Tuo1e7wY7Jw z+qsz{*i)4Cqb+#+Q*`6&xa^__>pf}B_)CUS zZAasAMJBCb21aau&7UHsuqW`LqMY!s4n(B2FEEJ~>(go-U`;brF^U}uI~rbx&T(ip zA>U=iqJ_AOa; zYsRiI?3%f2JiCtCWni)3ibrnYI8KrAqXnT@(_J>gKrn zj)7lr4za>5>pl{r%um^T!@^vxl&Z{l)AnrvUp({G=IEr`Y7nipuC>vnp!%k*nRX6bnhXS*oGqVA?BKYDlT{+$$Vg>$j7lom?4O-`XO zrJ#)4D7r+BrZ6XHrIg8X3WXJd`ziV|Ihw+ppo3ES;qoDy!V1Ad6n$KdrZ6XXgi?Cs z6biRyM%!3`U4nDSBxGYU7qj2i6ZNPQnvw_7pI?nUzoj-l@VIdLC;m|sS4D9&y~nJ$d2@jA?t;Z-rA8HrJ)hnbr_$m$yp0<3@PUJrTo z+?ybcOp~Bf`}YNO`*Nv)DjVl})IxeWpQqyHEge%LGxXu9XIWlU3~q-;l}mq@N$H`{ zcxWMRf0QjJVsd4@|GOwX^0jZ@fw-~|ODdDT50#j**6m-R48MOJslt}40z-no@1TShxQi;g)+MmcRt?T3If=3!JS- z%?gaU8;dz(!sFm8X9dP@wn{(1a+a(ro!18eGQKQ@SAK(2~@#9$)V>V|ZrpMd?6@?zjF=jS5K@aIl zs6cOjU>R)zm`g)xofLScWKvVqQWrrZDcn!#Y{u38<5rZI-KNR&A$qO|XCBC~zk{cU zc;wx`)>0!5JJ(#gkTA^6m4$IBX33Jv7nH+u3va0N%UoN4(I{cBe4k`t*^lpOV5GN5JmO zrEPDqnhp;KJsbLho%wjlY?4p}o4nd`eo9~n$k9CUYD3HU8vqgCV<+%4m6 z&j3zE$64hk?Reqw45D#tPPe%KXD!HX-To)w9b&I+%8_vnh;RYg=BBh3ryx|&HaE4kI2EDn zwz=lkVlzS$+vcXV7N;RJu5Iq1*5W}3Wwy;7+*&+1xF4@2q;KmCJaJ3+=hWGGvGvis z7@cXEG95~1tQxR){uZ{y+l=i?BmZYgPMIduulFw z$bact=#(XcKCH&6R|)ylbh^%)$Z0Yr;l0XBfXe4m*J|)7x=!Y;UiCDyk*?w`U*usm zj;{Ln45e)Pbfa)4=#hx*;M}5)H7<27WW2li7nkCZj zw&S5lkd1^uE__`t_mJp{%xpH=W<~ z!?Ka{j@%^Ym{=rV$1B%S%JtYyXBL?Rf>Dc2LIL1Pz)b-4XJhNRGi`!R*%%7Jscydu zUj@>}?D)7Aj;FME@#3W=l}pR$pu=*NlwAW2>l!>w4K^3{pTy^cw&<)DVLf&n&&r)_ zt8rqVcI;f^s&%{8;e5pPb$%a>9?!Z=!sFEd^@{LOU6`d&x&r-5T7fQ3P19e;=+ajp zT(98Ul(Un>yjsDVm1_sC^jix9+8RCVTf`f&`MSqe z?`fh<*w;JPxv?R8gU@}@K{lSgwr)*rEQ9r)>bjabceO365qtVhnXF1g{b(t!TBxE= zVi9cYuE$P?)thWJcqDHT6lF<7lh=a*ome6of8#R~NqwEqPbt+H_`RM@?rMF3CTn zMhLa35ff_kJAk#aCjj37+zVI>SOq8sTn`unFau6~-z5AI&;QZX&;+OiEC0h0b2m80Xcwc00Q8TSZnD7 z`~>g-pb=06xCS5q-uaG6H~`oISPhs87!CO35%30d0$KnSfVlu0KmeS6*d)9Qcopyr z;0eGZfO`R}0rLR4fU5!NfDgY7S%Ce39e@y^8Q=zd8DIl^{4JC4C%{5f;ToJmTD1vZ zu=?B^0&eW~E-Y@7V~EOWa6w2K=>@g7YVEh-fj#u%)u> z7;%29v$4_ZY4p}%-$ff8Ho;ihJYF02%UtjD3-j@1I6FfDU-sJS);HF>X*UWIL4Db^ z4s+j)aDKEUk1*F+Z38!#yE@g8*~Lc%6ezRCR;uFRy{lOflPq1z<7;Lf2~lCV@ch%CG*SW($%s9N(F4W z>9TDIc>GQqZ7SrhMu9Y50qm&l<0vZ;cG>oU3Oy9I+{E;CuWzJ*%wzMSpR%f}_4xe4 z0{mhA0ON)*pDaq>!pn6Ge-hmqG?`p~`FxN%7oD=Z&L`lojRxfu3$MEds#WWn7;WEB zZG-q|t{l_&gVv3mpj~d?Olp{4#Az6f^>t{k7yp&WS!uHsCvC=R^P$RdjE1i^;IF0v zmdiN&A5|fSQ&JW>@_47>UU?c!Ru!m$KG4DGa_bsURAKS<6Z#e=AUIFf=ttXw(LuQ= z6PZR;fEu{73P)`i(W&ux<*CWzU1zICA83rG2MZh{;`XC2$$3!5Km&X(<^t5%b81cWkBKhxu^QZxcx!=cLJ@k(WpGl`+l_(c;$Mf3izQ(OsZ8RfB*Ng%%j=d1#ib6m)5)7 zP_OpT=9uV)?R1C4cX@mgnssg@EdKIJnf1StWM^i%mD10=HVBiMELFX5F9NA7)uVEw3 z07nZ~iYqLn#gS^&aYAD_dL6D;CPA22U{nL7rmv`2;`R7^Q)Gm>Ou6vxW=zZAv6(&K zZcyyGG+ZfUtS_=gMPruQ9m{CXee_jk$OcR(1@vDM%;s4%LdsJyL2DM(s3XSKxzU3i z2id6Ts;#S6#z8h}kOWqP7_`}mMHq}7&2E=6!YU(ivD!Y^e3I22jTTA)NC`MlcI*Lu3S4; zUA>vY+RG}XuKpWy#}k0o=P}>P78aLOE~zMWEMHinq~DKtJ+$j^3SSJs4BN6fRunI% zcL@^zN`$hd~UTEOG-RDdt8ZUXOHXhv@2nD_PCOtojtD0 zvsj5|XOHXRZMt|{%#UsIt@}d-pf|9_0yYz({Z7U_C%?6bB^7;LzP5JD*R}oZ+OjRX zn9_SMbFu5TKR(Q^?pI!+tF3ZJ%3NHfjSFVb^|6P>t;e2AHDnL zd*c?(`^FokgZ^-`Wu8iRMMK(eezWhJ^Z)V5O?|0L3rcgZ%K2nL)4jLNe%`m`&r3%K zo)wQi{Hxa2dmmhrZwWEP_}n z_N8DhGR#FjJzR?B^QIT!gMuuymVXs8(gYJA1&|6b1JVG40D}SPfFXd3@wbR+!cag4 zU>IOHzzPsV==JXegECsiLOvh^FbpspUolE44wEkK@xngaPO^4n;6 z0(zx@Pk3m!2g!~ zyCCas0L^#q0c-)Z0JZ||1q1=x0NVjy1KbB_1&Dw)KnQR@pdIi4;Ol@6z=MEq0KN%$ z2=Fbyw*e0W9szs@@Lj<70FMH`5BLG#hkzY`oq)#xy8u4|{21^Pz)t~>1AYehIp7z7 zCjkEe_$A;;z;3`Ez^?$m20R7$4d7|OUcf%UZvnppJOg+Z@O!{>fad|7fG)s(KsVq3 z;6DKe0X=|2fENHS0$u{V40r|bD&P-*Uch0%Yk(tw*8y(;-UR#+a1`(s;BCM=fIk8L z4EQg=UjWAd#{usG{t9>x@HfEwfWHGIKp)@(Kp1cW@ZW$B0UrTQ0!{%w2Al?*0sI4S z7H|&G5BLP|Dd0R{0Pq>$0w4mQp9zoxNClVyX@Eh1!GLtY5P$_R6p#TJ1{e;o0x|(3 z0G9zq0!9Hw11<+#0T=@q3m6Bu5-=Wc72s;XH2@o60^kdPYXK7hlK|HNz6i(yOa@#J zxB+k@UsB1TYWa z0L%y63|IhI2>3E!5nwT(6tD!a6i^0O23QU#2dn^809FE40d4`@3b+k$JK!sTI{m%n*sL#wg6fHTLJe1f`Dy+?SQWV?gO*}L_ixL1h^m24tN0YbwCH; zLBKZv-vm4a_!i*XfQJE(0KNnGF5r8BM*-gl`~dJnzz)Dpz+-@2fFA*V4EPD)r+~)+ zKLh+6@C(2bfd2se67VEoH((FoSAbswo&x*^@HAjAU?1SOfZqY00Xz%%J>WUO^MFo3 z7hpf28*l*dpMZma9>5{M3xF2^F9BW#yaIR?@CQII;4t7dz!AXffHwed0{#d%3U~|f zHsBq=p8$Ub{1@OafMbB;fOi3Z1-u9N8{mDw-vJV!5AXpX3^)P!Z@`Cuj{qkDrvM)V zP6N&W{sA}(I0xtld;<6sa2_xK_zZ9X5CL53XT#^0CM6SH3APZ<{!rk5?^o=_+aP$K z7w?T!;eEHd23LImuZiF#PxitZy~@d6#K@B0`osA*Qv*J)wjo%yGYhX~Hu!7kEJFI} zPPD7r#(v&J()J9gotK1Gjrk2WnjOD9nVQ!;-X4aR^kv% zXFQ80yx1AQp=WZhx&#ZKoI9QieZ+t_Sq+*hmqG1a$s5lEAH-DG7^)J@g!*y<4mI@w zU+w2AObyE#&te1G9bTL^D2c91q9{t&CrN;Pjh7krS!J`wsH zT-2-ynE0H2|0W}o=;|%2j^&K!pnM?od0L2^HJ-&9Y|q%R(O3!g1rh{g$r z`inPomNlLOIw5T$`MFBLvL<9f--a8o&^i4q>hX=6;yKWMUJ7}F{>lxVg^cmk^obQG zk8M>tYRVW-?(pVV1 zi&zG73#7y>zY9$PEfN$L4sasaYwjl4Xpi z*N^S-^tx)OWI5v*U{KL~c02=}0+cbH953{0?Kqx1W_E#he`|@2I{_oMy`0(};u?g~ zR&lg*z?-l+Dm%m}3}Wknld}&GYdTkoVK?kZ}4GRtA#=lqr zQMHX@Dv1?MqvI8Y^m=@cZ%|vhVq_s_0~CoBRq1Q2WBmU`RrTIMWtBjRYIA0TSRK^F zEF6HIX#7WAJJ~l{temmb(TzgR7_R|x;kyXedZ&+C=Ra8=QQk*o6E7p+_u_LOqsq{^ z5IJK!19<37KKgVrVL{b_f@WKCws;Oce30idY!5m=oh9dt=Ku#?jq{)D@d4gfsVx7b zsdVijic`Fx_0H;zb(+hLtyP`1l#B^D1R8W>=fya%jPVS}h0?bua+O_#ftoR%1^c8# zt@1d3r#Cpu8P8x{ol@Niy9RH0lrNqdHg2rbhMm|FaB4YUJUy#vL-qBJt|Z{a_jP`H zpt;zCHk!a0;lm`Y^~Vokny9Qw$_U4UZUAoaO2OTMKRyScx#d6E{-RtbXOuulgZ$}I z;=1RQl#U(;16E?lBokCBlyp{cLOay2NBR6tuRN)Yt(1$hRw<2nS2sGnh8?_KS12e= zS;5I!ZOE>suDNmzzVE~!u;!mPc8oY;CYMg*iBToNT(1U4a%*LX>ooOR5i#MUobi>% z8m8f7G0Of~a^8eIE;8@eDFFrvr~MSqixF4NycBZA#2V1aDQwVD3*}sHx=f z9Ztufl5!?iz<9MoM^7cFMMYYQ>7EXsFJj*SI!qlOlF18uK1+!nRi-*LghbDJXcl9! zu(?Qd0*<}TkBN!3s5aNJ;I6X{dly9IkEhoy3|i#j1ZJQwFMTp$JVmTTZ4k0l;!WL}zH^}isOXPP?d&rx7)8f&R6!>8VhnY+ir8aghAd}%|S-iA(o_?iTj;M9kPu+-bB|J>X=di;{zCq~EJWlEb!SuW1NmE87&X6dfLu zTv;8Pp%QncQ5-5|?m8ZoOo-QA4{huGjDd;}EyB*I6d6;Ja=hQ{l*1s7PYXIg@O*3&-cNC(FoeNTGzfRqJ%qm(3|lk?pCY zjonX|o&axsC{HI_lz`l!+3wo<#wQP%|m%taGT2 zll&xn8*^|h4i2ZPpo*Da?nSheVbl6id>@k24Ed3Og8V4A$@9vg7!7pEw3UFxNC3F9 zUruvgPChmw;%->SLCL|2XfKEv`E0h8v9$y_z zFAN#D6EnE^b2F%M#g>Tay%6NXOcquPkm%O!UE7dXAuw8My81|tUc+_+&$ zWuV*`mU!wtUTtVL<&sY)Y^Rno%puD^L~BY0fQRt&ENBF|5>HlvfyMFKWRtKc&HQ-dZ}g zJ}}JfjA!ks#(=rWQ1>d;a+txm;_kP!2jhkkHF`}ynZ{6{OSLg(iSYvY^Ls;y8UszD zLS!T|-!9h=YG+1OL0lPi%K}C+r`M=8tjScV420%ZRV#*t>w)~3DP9(Tc4Q!vGtflZ zFxOV-jHJ5Vs-h@tymk4*Oe3kBfhM-HQXw-ET4PsR9fan`6*Zu}1YjhTGtdi_$qa>R z-Xf=YM!bEsw-*d$YV3C@lINPHc1KN~Ok+ThUpQSgs74^aJ7S>Wv>DU5L7ef`uD!QmAW%oI6R5mtVklE} z!5F6!I2ryh+fb;=K$A)SRG~8>Du{2MAZn_0`tk0Ik*b9lNZF5u)OhpowGXa|nxE#N z)0`lR`9Y{WWsixMX9?4s2|SCkC}mW#LO9Ks9}O>9CeYtPBZn%&t+uKh+P^8b6UZ;u z827qqPK{oZD$^J#ST$X3WEue}NU!@C>8KlX^FbY}BY)AyNTw^%prDW$2%Vmz^A6ME zYg>KU$JjJF4!V+RZwVR7)z3QP-Jteng+W=T=V8F|{h4Q`U-+n1BJdNgiaI&w`(Mq(sRoh(zw$Bjx{Evqk8 z85J>KwW%hKMpvwo8VXdJ6=i6Sf$@$-rZEyYEsuLLxcAr+%CE5)Nt+hG zK2Qk?CRqbW#XBsCt zAiL}th4GU7E@cQ#Oi@q}=d=AT>~&sUg^D(88U?CJI8#=lx_`Drmncs`UYvHW8u+G5 z$iba-%#TrB!rt-o7gr5UH@(g#qng#41{bHj7HLFNfK`;3AJgulrkfGp7nz{OHC{N3 zn4i)ZX2cths+PU8X-MOW^M8!yVsZ-O=iJojJg{0~x9QaSd`|Q9c!$oPRmPM{ncB|a z^jZd)#!#Tv$T4w<7pT5&YGf2=`~u)beOiM2@|&uL`r)=w{w35;eVx@%Kit#B{1gWo z6RR`~XMBG7MOZ`qs?=7IP(Ky);kf{ll(-vI zt3`q>6I304|3p(n2`I(W=y|$KV^n$We646X&GB7do5fyonff5Xr!mdMsp{jO&4ta&# ztm|*Zhpb1Dlc$>Zk59i6By_&sJx2C2r(&vP7aO^Vx3VQPUUvRk>0 zm&^%`f=`TRwzwT}&KdSA?(MzsaKNksHn-X=fQJCXC#c%e^ohS`tKC(&3XA7GZMOP< z`P7Gv0OmI5-MpG7E4-#2gu^nbdJ=G#aR-1LhA-E?ra0>7E+3ZZ!#Ga7l*`RhL$N}xA0tg1uj(F)nmb{^m-DnO7;(+E;ZXXC|su~pz zk6cbm3i%NoB!dn>C?F8g&WUiyG3se0St({WKF%p^Y37J_wI40FHrFZSHpX-nAo?h$ zi@_wmII>j-C${5qE6EyhCRJW*9SwWlDlTN~)_D?|<1&MSK3^P5xtYm&xU1um<@{xdSJrCfV=pP&i_8L!-W&XxJP) zF_?#3Uc(!@5?kRLs6-i403u(6U!n=>ai7FoHY9^ z8in&Q1taI79RNr?LiyB2!EJ)t(T`LLD1Pozyr%T~Qjc_@-!&D;- zXOCYmF0O{X4h(^b7+zY81<dy zjEQ=}XTU$9~9@lP!H4x7 zQWGWD!BTGIOZX%q!@x(dF!(p2D;UX8DM!fUEDPnP zEVr0`r&|m+62B~aC;=o5rODDr2|vg+X~=cdQh>%$o29`KTQwyhK%aKO5m%Z+fWQa% zf%{cRgRRrT_nr92zw7#ZdE~Xu3(P&%mrLZ)6(EiZ$Jt-wX#=>OfIx4gONoYNav_ha zV-CSQk&u>yB2gftkS@@QONmUu+{vT6HR)u{m9;UnF_HMg%bhI|)&P!<=?1{y(Y-X5 zhL9IAN~IKerDN35of}RZH}rSCQ55-r?rd#`)q584*<08Zz3Z(W$H#PEecl+=QB=+7 z4n;U$>(F3lAMm{fq)+y-dw7p!?b^MY_IRoEZhCixn}G-0Ux*f?^?nT2NtvUIf4qG0 z&mSd&RFb=%Ab<4na`9&jC9-0lMAR+=YL(d8AyBudVuKJ9YFgjku787%93=|U5{vXS z|4Zt3bLDs8hai=i1)cTUi5|#ZFDIevzYmnS5}3Y8T-o`^FxSgT-1_eW#jONPTqGJJ zJyf3seCAzdy)TQFOvU4zrJEY2=EHuKQb89Tsg6>j{WV#I#@k5++XgR0mbaJViEbq} zHq|Q#2l)NDgY7&F}Tgn5561JOaZd6FR5g+t7- zS>_(|LnzgF)B_gRAHGD7$>zlNc-f(cnc!pc`QV?_g9+$N0|Bjr8Qa7VfefYMqI-T~ zxu7yL2DV-aqjN^`jqMQ_`#pcQxp8QvGEKFzUnDdVva~rHtNCyuFdgPkDFql!2ggtO zE|F=QW03f;Sn`!D^uw^auo6BSPOE9LC6A(&HBoW&GcVekeO18qmudRAk@8JAk+}=| zsazGbSWM4yd68Q5Dgy6`&!{9udob^>Zi`^d?r3$!&dh(Zf4~izq0{!=`sO-}8s0em z+II|CkBww^vvGU`8udP+U^9{}9*yFj6^pDe%hYSs=7TKV(;eE;(8v**(fFJ3->{2kgr zS;GxlxF-L4{qvgwLHP&hSJ36Hj)j(@@YG8^iaF}FNv59uH|pirN6Y_iv+01(G!&(_ z1fTtD?8vN%HhVKME})Bg9xGJFa~kg3lJJfnMW_9J+jP!l8D2&jRx z#4V9(8SYPo)eR1h&zapuw9P{>K+5g88OE@SM=AY+-rk_x%k9cZNqX5W zaJ;9h`QzGVq2Ya9*?}tVXmm%mE7w=D`83NB9-{AGzk2ohN6fSQ#paRrT<>q5MPl0h zO^TVIMwB%Tzko*M;OsFYJ8e;FrNsiysxWQ>D#7scA>tZyr3y{6lp2dAp51ZWlvkSJ z=R+hm=1Mg))Y5XX)U&(XI?C`Trnt%GJ96m}zXxRo;b` zunXm#H$mJIWhA374;bLeT#0JMzgS+I`fPiN{kO`Xem+EAW3FVt@CIvAu!_}8A|`C8 zkSp=}`4Cx$C(czv9Ht)_z`ea)nPq3Z#Toh)?#tg9Dn7BKyO6>cz<_Yrwf7_RM9A=^@yrk(JQwnE}Ejhv94ErV9}2Q_4K7RAVHP znUqPS{t&}ePzu``Q3Af~P(Z+G zaHJ2ugit`h9sdymt{hN+Hw%OIz}5B%@aE#9cz3W!=k>Ae5)@tz&T}ChF2ISoekcVz zi0TOq@UDyn89-OlCBU1tK>7RnECB&MwdBu%(>4RVxhAOqu0TnEH^qYD8%HI*hbIqzc{;BXKz;m>Ujf3!IG@)DEW$cK&#MEyQ;06d=T0M7ZHAME z#wip?u{*U1MUt~ycMm7Adb+P9##1C0bsONXR)!~wLLW8~sHm%bm4Z-7xa-k$3-fpoW!7juv%@x;VF9+zk5^`yhuhb^%TRDF4P0E$p z-$`0ISaDhpdblqE}8K8LM3Ru&h6X2rw|+mPXAY|+Ciny z*@h}~B>d5sVj@@6$8|rT-hI(93tE<#kJfv>sw(@ zKt@w*>~VxEr+}}mvBwc^(;M`aQxMdz_uyW5BM*H^v?(U?m@@0Ijjd zDY#vVh|m~&oPY&jQxO_tj}vfP9Fd?g_Ba8{iJ%gAV~^wA9!Ui7#vaGd=UO^rkHg*8 zO4zr?9;F~rwfT<@fH(Fy-tEpr0&nbbybJFk_{P}d1l$Hs2zX*_ozH?u;Eg?ww@d|k6TGp<@y_W% z_&Z~d!>u|DWnYawiuV2z!rU2q9IoY2^4`=hJ2wDaEB<9O$; zAp%rmkE1P~m~wZ<9)~+u4`JRJdz^$7R-h7iV~^vVw}^<)7<-(6l^>u248|TO;(Sg- z3UBOjyhXNgA*^PIy=#=KHQsea + +OBJCOPY= objcopy +OBJCOPY_FLAGS= -R .dynamic -R .note -R .comment +OBJS= init/entry.o init/main.o hal/video.o hal/io.o \ + rtl/string.o rtl/memory.o hal/intsupport.o hal/interrupts.o \ + hal/timer.o hal/keyboard.o hal/timestamp.o hal/counters.o +RM= /bin/rm + +all: kernel.bin + +kernel.bin: kernel.tmp + $(OBJCOPY) $(OBJCOPY_FLAGS) -S -O binary ./kernel.tmp kernel.bin ; \ + mcopy -o ./kernel.bin Q: ; \ + mcopy -o ./kernel.map Q: ; \ + $(RM) -f kernel.tmp + +kernel.tmp: $(OBJS) + $(LD) -M -o kernel.tmp -Ttext 0x00010000 -e StartOfKernelImage $(OBJS) \ + > kernel.map + +clean: + $(RM) -f $(TARGET) $(OBJS) *~ #*# .#* ; \ + mdel Q:\$(TARGET) diff --git a/kernel/defines b/kernel/defines new file mode 100644 index 0000000..dcee875 --- /dev/null +++ b/kernel/defines @@ -0,0 +1,6 @@ +// +// Symbols defined here are globally defined in all Makefiles below +// here. +// +#define KERNEL +#define DEBUG \ No newline at end of file diff --git a/kernel/driver/keyboard.c b/kernel/driver/keyboard.c new file mode 100644 index 0000000..7410171 --- /dev/null +++ b/kernel/driver/keyboard.c @@ -0,0 +1,37 @@ +//+---------------------------------------------------------------------------- +// +// File: keyboard.c +// +// Module: +// +// Synopsis: +// +// Created: sgasch 21 Oct 2003 +// +//+---------------------------------------------------------------------------- + +#include "kernel.h" +#include "hal.h" +#include "rtl.h" +#include "interrupts.h" + +void +HalKeyboardInt(INTERRUPT_STATE *is) +{ + BYTE c; + BYTE k; + + HalDisableInterrupts(); + + c = in(0x64); + HalIoDelay(); + if (c & 1) + { + k = in(0x60); + HalIoDelay(); + HalVideoPrint("Testing %x.\n\0", 1); + } + + HalEnableInterrupts(); +} + diff --git a/kernel/driver/video.c b/kernel/driver/video.c new file mode 100644 index 0000000..a63331b --- /dev/null +++ b/kernel/driver/video.c @@ -0,0 +1,503 @@ +//+---------------------------------------------------------------------------- +// +// File: video.c +// +// Module: Video driver +// +// Synopsis: low-level interface to video memory +// +// Created: sgasch 5 Jul 2003 +// +//+---------------------------------------------------------------------------- + +#include + +#include "kernel.h" +#include "hal.h" +#include "rtl.h" +#include "video.h" + +extern BIOS_HARDWARE_BLOCK g_BiosHardwareBlock; + +// +// Local #defines +// +#define FG_BG_TO_ATTRIB_BYTE(fg,bg) ((fg&0x0F)|((bg&0x0F)<<4)) +#define LINES (0) +#define COLS (1) +#define CRT_ADDR_REG (0x3D4) +#define CRT_DATA_REG (0x3D5) +#define CRT_CURSOR_LOC_HIGH_REG (0x0E) +#define CRT_CURSOR_LOC_LOW_REG (0x0F) +#define ZERO_BASED_LINE_COL_TO_BYTE_OFFSET(L,C)\ + (((g_bScreenResolution[COLS] << 1) * (L)) + ((C) << 1)) +#define ZERO_BASED_LINE_COL_TO_ULONG_OFFSET(L,C)\ + (ZERO_BASED_LINE_COL_TO_BYTE_OFFSET((L),(C)) >> 2) + +// +// Global identifiers +// +BYTE *g_pVideoMemory = NULL; +BYTE g_bScreenResolution[2] = { 0, 0 }; +BYTE g_bSoftCursor[2] = { 0, 0 }; +BYTE g_bColorAttribute = FG_BG_TO_ATTRIB_BYTE(BLACK, WHITE); + +typedef struct _VIDEO_LOOKUP_ENTRY +{ + CHAR *szBoardName; + BYTE *pStartOfVideoMemory; + BYTE uScreenLines[3]; +} +VIDEO_LOOKUP_ENTRY; + +static VIDEO_LOOKUP_ENTRY g_VideoLookupTable[] = +{ + { "No video adapter", + (BYTE *)0x000B0000, + { 25, 25, 25 } }, + { "Monochrome display adapter", + (BYTE *)0x000B0000, + { 25, 25, 25 } }, + { "CGA", + (BYTE *)0x000B8000, + { 25, 25, 25 } }, + { "Code 3: Undefined", + (BYTE *)0x000B0000, + { 25, 25, 25 } }, + { "EGA with color display", + (BYTE *)0x000B8000, + { 43, 25, 25 } }, + { "EGA with mono display", + (BYTE *)0x000B0000, + { 43, 25, 25 } }, + { "Code 6: Undefined", + (BYTE *)0x000B0000, + { 25, 25, 25 } }, + { "VGA with mono display", + (BYTE *)0x000B0000, + { 50, 27, 25 } }, + { "VGA with color display", + (BYTE *)0x000B8000, + { 50, 27, 25 } }, + { "Code 9: Undefined", + (BYTE *)0x000B0000, + { 25, 25, 25 } }, + { "MCGA with digital color", + (BYTE *)0x000B8000, + { 25, 25, 25 } }, + { "MCGA with monochrome", + (BYTE *)0x000B0000, + { 25, 25, 25 } }, + { "MCGA with analog color", + (BYTE *)0x000B8000, + { 25, 25, 25 } } +}; + + +//+---------------------------------------------------------------------------- +// +// Function: VideopInitialize +// +// Synopsis: +// +// Arguments: void +// +// Returns: STATUS +// +//+---------------------------------------------------------------------------- +STATUS +VideopInitialize(void) +{ + BYTE uType = g_BiosHardwareBlock.bDisplayType; + BYTE uIndex = 2; + + // + // Based on the font size (width in pixels), set the index into the + // screen lines lookup table + // + switch(g_BiosHardwareBlock.bFontSize) + { + case 8: + uIndex = 0; + break; + case 14: + uIndex = 1; + break; + case 16: + uIndex = 2; + break; + } + + // + // If the display type returned by BIOS is known to us, use the + // lookup table to populate some globals here. + // + if (uType <= ARRAY_LENGTH(g_VideoLookupTable)) + { + g_pVideoMemory = g_VideoLookupTable[uType].pStartOfVideoMemory; + g_bScreenResolution[LINES] = + g_VideoLookupTable[uType].uScreenLines[uIndex]; + } + + // + // Otherwise make some guesses + // + else + { + g_pVideoMemory = (BYTE *)0x000B8000; + g_bScreenResolution[LINES] = 25; + } + g_bScreenResolution[COLS] = 80; + + // + // Set color to WHITE on BLACK + // + VideoSetCurrentColorAttribute(WHITE, BLACK); + + // + // Clear the screen and thus synchronize the hardware and soft cursor + // position. + // + VideoClearScreen(); + return(STATUS_SUCCESS); +} + + +//+---------------------------------------------------------------------------- +// +// Function: VideoDriverMain +// +// Synopsis: +// +// Arguments: UINT uEvent +// +// Returns: STATUS +// +//+---------------------------------------------------------------------------- +STATUS +VideoDriverMain(UINT uEvent) +{ + switch(uEvent) + { + case DEVICE_DRIVER_INITIALIZATION: + return(VideopInitialize()); + + case DEVICE_DETATCH: + ASSERT(FALSE); + return(STATUS_NOT_SUPPORTED); + + case DEVICE_DRIVER_SHUTDOWN: + g_fVideoEnabled = FALSE; + return(STATUS_SUCCESS); + + default: + } + return(TRUE); +} + +STATUS +VideoDriverRead(UINT uAddress, + void *pBuffer, + UINT uBufferLength); + +STATUS +VideoDriverWrite(UINT uAddress, + void *pBuffer, + UINT uBufferLength); + +STATUS +VideoDriverIoctl(UINT uIoctlCode, + void *pBuffer, + UINT uBufferLength); + + +// +// Move the hardware cursor on the screen to sync up with the software +// cursor position in g_bSoftwareCursor +// +void +VideoSetHardwareCursor(void) +{ + WORD wOffset; // byte offset into video mem + BYTE bOrig; // original port contents holder + + // + // Cursor is zero-based (e.g. line 24 is the last one) + // + ASSERT(g_bSoftCursor[LINES] < g_bScreenResolution[LINES]); + ASSERT(g_bSoftCursor[COLS] < g_bScreenResolution[COLS]); + + wOffset = ZERO_BASED_LINE_COL_TO_BYTE_OFFSET(g_bSoftCursor[LINES], + g_bSoftCursor[COLS]); + ASSERT(wOffset <= (g_bScreenResolution[LINES] * + g_bScreenResolution[COLS]) * 2 - 2); + + // + // Save original contents of CRT address register. + // + bOrig = in(CRT_ADDR_REG); + + // + // Set the high cursor location byte + // + out(CRT_ADDR_REG, CRT_CURSOR_LOC_HIGH_REG); + out(CRT_DATA_REG, (wOffset >> 8) & 0xFF); + IoDelay(); + + // + // Set the low cursor location byte + // + out(CRT_ADDR_REG, CRT_CURSOR_LOC_LOW_REG); + IoDelay(); + out(CRT_DATA_REG, (wOffset & 0xFF)); + IoDelay(); + + // + // Restore contents of the CRT address register + // + out(CRT_ADDR_REG, bOrig); + IoDelay(); +} + +void +VideoSetCurrentColorAttribute(BYTE bFg, BYTE bBg) +{ + bFg &= 0x0F; // restrict these guys to colors + bBg &= 0x0F; // and the high intense bit + + g_bColorAttribute = FG_BG_TO_ATTRIB_BYTE(bFg, bBg); +} + +void +VideoClearScreen(void) +{ + ULONG *p = (ULONG *)g_pVideoMemory; + ULONG uFill; + ULONG uCount; + + // + // Start uCount at the number of ULONGs in visible video memory + // + uCount = ZERO_BASED_LINE_COL_TO_BYTE_OFFSET(g_bScreenResolution[LINES] - 1, + g_bScreenResolution[COLS] - 1); + uCount += 2; + uCount >>= 2; + + // + // Construct a fill ULONG: blank attrib blank attrib + // + uFill = 0; + uFill |= (ULONG)g_bColorAttribute; + uFill |= ((ULONG)g_bColorAttribute) << 16; + + // + // Blast uFills into video memory + // + while(uCount) + { + *p++ = uFill; + uCount--; + } + + // + // Set cursor position to 0,0 + // + g_bSoftCursor[LINES] = g_bSoftCursor[COLS] = 0; + VideoSetHardwareCursor(); +} + + +void +VideoScroll(void) +{ + ULONG *p = (ULONG *)g_pVideoMemory; + ULONG uUlongsPerLine = (g_bScreenResolution[COLS] >> 1); // 2X/4 + ULONG i, j; + ULONG uFill; + + ASSERT(sizeof(ULONG) == 4); + ASSERT(sizeof(BYTE) == 1); + + for (i = 0; + i < (g_bScreenResolution[LINES] - 2); + i++) + { + // + // Start at i, 0. Copy a ULONG up from i+1, 0. Do the whole line i. + // + for (j = 0; + j < uUlongsPerLine; + j++) + { + *p = *(p + uUlongsPerLine); + p++; + } + } + + // + // Construct a fill ULONG: blank attrib blank attrib + // + uFill = 0; + uFill |= (ULONG)g_bColorAttribute; + uFill |= ((ULONG)g_bColorAttribute) << 16; + + // + // Blank the last line + // + p = (ULONG *)(g_pVideoMemory + + ZERO_BASED_LINE_COL_TO_BYTE_OFFSET(g_bScreenResolution[LINES] - 1, 0)); + for (i = 0; + i < uUlongsPerLine; + i++) + { + *p = uFill; + p++; + } +} + +void +VideoPutChar(BYTE c) +{ + BYTE *p = (g_pVideoMemory + + ZERO_BASED_LINE_COL_TO_BYTE_OFFSET(g_bSoftCursor[LINES], + g_bSoftCursor[COLS])); + switch(c) + { + case '\n': + g_bSoftCursor[LINES]++; + if (g_bSoftCursor[LINES] >= g_bScreenResolution[LINES]) + { + VideoScroll(); + g_bSoftCursor[LINES] = g_bScreenResolution[LINES] - 1; + } + g_bSoftCursor[COLS] = 0; + return; + default: + *p = c; + p++; + *p = g_bColorAttribute; + p++; + g_bSoftCursor[COLS]++; + if (g_bSoftCursor[COLS] >= g_bScreenResolution[COLS]) + { + g_bSoftCursor[COLS] = 0; + g_bSoftCursor[LINES]++; + if (g_bSoftCursor[LINES] >= g_bScreenResolution[LINES]) + { + VideoScroll(); + g_bSoftCursor[LINES] = g_bScreenResolution[LINES] - 1; + } + } + } +} + +void +VideoPutNullTerminatedString(BYTE *s) +{ + ULONG uSafety = 0; + + while (*s != 0) + { + VideoPutChar(*s); + s++; + uSafety++; + + if (uSafety > 256) + { + VideoPutNullTerminatedString("RUNAWAYSTRING!?!\n\0"); + break; + } + } + VideoSetHardwareCursor(); +} + +void +VideoSetCursorPosition(BYTE bLine, BYTE bCol) +{ + if (bLine >= g_bScreenResolution[LINES]) + { + bLine = g_bScreenResolution[LINES] - 1; + } + if (bCol >= g_bScreenResolution[COLS]) + { + bCol = g_bScreenResolution[COLS] - 1; + } + + g_bSoftCursor[LINES] = bLine; + g_bSoftCursor[COLS] = bCol; + VideoSetHardwareCursor(); +} + +void +VideoGetCursorPosition(BYTE *pbLine, BYTE *pbCol) +{ + *pbLine = g_bSoftCursor[LINES]; + *pbCol = g_bSoftCursor[COLS]; +} + +void +VideoPrint(CHAR *szFormat, ...) +{ + CHAR *p = szFormat; + CHAR *q; + CHAR buf[32]; + va_list args; + INT iVal; + UINT uVal; + CHAR *szVal; + + va_start(args, szFormat); + while (*p != '\0') + { + switch (*p) + { + case '%': + p++; + switch (*p) + { + case '\0': + goto done; + case '%': + VideoPutChar('%'); + break; + case 'd': + iVal = va_arg(args, int); + q = RtlIntToAscii(iVal, buf, ARRAY_LENGTH(buf), 10); + ASSERT(strlen(q) < 20); + VideoPutNullTerminatedString(q); + break; + case 'u': + uVal = va_arg(args, unsigned int); + q = RtlIntToAscii(uVal, buf, ARRAY_LENGTH(buf), 10); + ASSERT(strlen(q) < 20); + VideoPutNullTerminatedString(q); + break; + case 'x': + uVal = va_arg(args, unsigned int); + q = RtlIntToAscii(uVal, buf, ARRAY_LENGTH(buf), 16); + ASSERT(strlen(q) < 20); + VideoPutNullTerminatedString(q); + break; + case 's': + szVal = va_arg(args, char *); + VideoPutNullTerminatedString(szVal); + break; + case 'c': + iVal = va_arg(args, int); + VideoPutChar(iVal & 0xFF); + break; + default: + VideoPutChar(*p); + break; + } + break; + default: + VideoPutChar(*p); + break; + } + p++; + } + done: + va_end(args); + VideoSetHardwareCursor(); +} diff --git a/kernel/hal/Makefile b/kernel/hal/Makefile new file mode 100644 index 0000000..3725f18 --- /dev/null +++ b/kernel/hal/Makefile @@ -0,0 +1,20 @@ +CC= gcc +CFLAGS= -Wall -imacros ../defines -I../inc +OBJS= video.o io.o interrupts.o intsupport.o keyboard.o timer.o \ + timestamp.o counters.o +RM= /bin/rm +NASM= nasm +NASMFLAGS= -f elf + +.SUFFIXES: .c .o .asm + +all: $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +.asm.o: + $(NASM) $(NASMFLAGS) -o $*.o $< + +clean: + $(RM) -f $(OBJS) *~ #*# .#* diff --git a/kernel/hal/counters.c b/kernel/hal/counters.c new file mode 100644 index 0000000..9bd9466 --- /dev/null +++ b/kernel/hal/counters.c @@ -0,0 +1,17 @@ +//+---------------------------------------------------------------------------- +// +// File: counters.c +// +// Module: +// +// Synopsis: +// +// Copyright (C) 2003 Scott Gasch +// +// Created: sgasch 24 Oct 2003 +// +//+---------------------------------------------------------------------------- + +#include "kernel.h" + +UINT64 g_ullTimeStampCounter = 0; diff --git a/kernel/hal/interrupts.c b/kernel/hal/interrupts.c new file mode 100644 index 0000000..94f2528 --- /dev/null +++ b/kernel/hal/interrupts.c @@ -0,0 +1,230 @@ +//+---------------------------------------------------------------------------- +// +// File: interrupts.c +// +// Module: +// +// Synopsis: +// +// Created: sgasch 6 Jul 2003 +// +//+---------------------------------------------------------------------------- + +#include "kernel.h" +#include "hal.h" +#include "rtl.h" +#include "interrupts.h" + +IDT_ENTRY g_IDT[NUM_IDT_ENTRIES]; +INTERRUPT_HANDLER g_InterruptHandlerTable[NUM_IDT_ENTRIES]; +ULONG g_uInterruptsEnabled = 0; + +void +HalEnableInterrupts(void) +{ + g_uInterruptsEnabled++; + ASSERT(g_uInterruptsEnabled == 1); + + __asm__ __volatile__ ( + "sti" + ); +} + +void +HalDisableInterrupts(void) +{ + g_uInterruptsEnabled--; + ASSERT(g_uInterruptsEnabled == 0); + + __asm__ __volatile__ ( + "cli" + ); +} + + +//+---------------------------------------------------------------------------- +// +// Function: DoNothing +// +// Synopsis: The default interrupt handler, do nothing +// +// Arguments: INTERRUPT_STATE *p +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void HalDoNothing(INTERRUPT_STATE *p) +{ + ; +} + + +//+---------------------------------------------------------------------------- +// +// Function: InterruptSendIrqEoi +// +// Synopsis: Send Interrupt ReQuest End Of Interrupt to the PIC. +// +// Arguments: INTERRUPT_STATE *is +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void +HalInterruptSendIrqEoi(INTERRUPT_STATE *is) +{ + ULONG uIrq; + BYTE bCommand; + BYTE *p = (BYTE *)0x000B8000; + + *p = '3'; + *(p + 1) = ' '; + + if ((is->uIntNum >= FIRST_EXTERNAL_INT) && + (is->uIntNum <= LAST_EXTERNAL_INT)) + { + uIrq = (is->uIntNum - FIRST_EXTERNAL_INT); + ASSERT(uIrq >= 0); + ASSERT(uIrq <= 15); + bCommand = 0x60 | (uIrq & 0x7); + + if (uIrq < 8) + { + // EOI to master PIC + out(0x20, bCommand); + HalIoDelay(); + } + else + { + // EOI to slave PIC and cascade line EOI to master PIC + out(0xA0, bCommand); + out(0x20, 0x62); + HalIoDelay(); + } + } + + *p = '4'; + *(p + 1) = ' '; +} + +//+---------------------------------------------------------------------------- +// +// Function: InterruptInitializeGate +// +// Synopsis: Initializes an interrupt gate with given handler address +// and descriptor privilege level. +// +// Arguments: IDT_ENTRY *pEntry, +// void *pPreamble, +// ULONG uDpl +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void +HalInterruptInitializeGate(IDT_ENTRY *pEntry, + void *pPreamble, + ULONG uDpl) +{ + ULONG u = (ULONG)pPreamble; + + pEntry->i.offsetLow = u & 0xffff; + pEntry->i.segmentSelector = (1<<3); + pEntry->i.reserved = 0; + pEntry->i.signature = 0x70; // == 01110000b + pEntry->i.dpl = uDpl; + pEntry->i.present = 1; + pEntry->i.offsetHigh = u >> 16; +} + + +//+---------------------------------------------------------------------------- +// +// Function: InterruptInstallHandler +// +// Synopsis: Installs an interrupt handler routine for a given IRQ number +// +// Arguments: ULONG uInterruptNumber, +// INTERRUPT_HANDLER pHandler +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void +HalInterruptInstallHandler(ULONG uInterruptNumber, + INTERRUPT_HANDLER pHandler) +{ + ASSERT(uInterruptNumber < ARRAY_LENGTH(g_InterruptHandlerTable)); + + g_InterruptHandlerTable[uInterruptNumber] = pHandler; +} + + +//+---------------------------------------------------------------------------- +// +// Function: InterruptInitialize +// +// Synopsis: Initialize the interrupt system +// +// Arguments: void +// +// Returns: void +// +//+---------------------------------------------------------------------------- +void +HalInitializeInterrupts(void) +{ + ULONG uPreambleEntrySize = (ULONG)&HalInterruptHandlerPreambleAfter; + ULONG u; + ULONG uDpl; + USHORT uLimitAndBase[3]; + BYTE *p; + + // + // Determine the size of an interrupt preamble table entry. + // + uPreambleEntrySize -= (ULONG)&HalInterruptHandlerPreambleBefore; + + // + // Set the base interrupt number for each PIC. See comments in + // intsupport.asm. + // + HalInterruptInitializePICs(); + + // + // Build interrupt descriptor table + // + for (u = 0, p = &HalInterruptHandlerPreamble; + u < ARRAY_LENGTH(g_IDT); + u++, p += uPreambleEntrySize) + { + uDpl = (u == 0x2E) ? USER_PRIVILEGE : KERNEL_PRIVILEGE; + + HalInterruptInitializeGate(&(g_IDT[u]), p, uDpl); + HalInterruptInstallHandler(u, HalDoNothing); + } + + // + // Install the IDT + // + u = (ULONG)&(g_IDT); + uLimitAndBase[0] = 8 * NUM_IDT_ENTRIES; + uLimitAndBase[1] = (USHORT)(u & 0xffff); + uLimitAndBase[2] = (USHORT)(u >> 16); + + HalInterruptLoadIDTR((void *)uLimitAndBase); + + // + // Install interrupt handlers + // + HalInterruptInstallHandler(IRQ0, HalTimerInt); + HalInterruptInstallHandler(IRQ1, HalKeyboardInt); + + out(0x21, 0xFC); // turn on keyboard and timer + + // + // Enable interrupts + // + HalEnableInterrupts(); +} + diff --git a/kernel/hal/interrupts.h b/kernel/hal/interrupts.h new file mode 100644 index 0000000..673b13b --- /dev/null +++ b/kernel/hal/interrupts.h @@ -0,0 +1,92 @@ +//+---------------------------------------------------------------------------- +// +// File: interrupts.h +// +// Module: +// +// Synopsis: +// +// Created: sgasch 6 Jul 2003 +// +//+---------------------------------------------------------------------------- + +#ifndef _INTERRUPTS_H_ +#define _INTERRUPTS_H_ + +#define NUM_IDT_ENTRIES (256) + +// Exceptions range from 0x00..0x11 +#define FIRST_EXCEPTION 0x00 +#define LAST_EXCEPTION 0x11 +#define NUM_EXCEPTIONS 18 + +// External IRQs range from 0x30..0x3F +#define FIRST_EXTERNAL_INT 0x30 +#define LAST_EXTERNAL_INT 0x3F +#define IRQ0 0x30 +#define IRQ1 0x31 +#define IRQ2 0x39 +#define IRQ3 0x33 +#define IRQ4 0x34 +#define IRQ5 0x35 +#define IRQ6 0x36 +#define IRQ7 0x37 +#define IRQ8 0x38 +#define IRQ9 0x39 +#define IRQ10 0x3A +#define IRQ11 0x3B +#define IRQ12 0x3C +#define IRQ13 0x3D +#define IRQ14 0x3E +#define IRQ15 0x3F +#define NUM_EXTERNAL_INTS 16 + +typedef struct _INTERRUPT_GATE +{ + USHORT offsetLow; + USHORT segmentSelector; + USHORT reserved : 5; + USHORT signature : 8; + USHORT dpl : 2; + USHORT present : 1; + USHORT offsetHigh; +} +INTERRUPT_GATE; + +typedef union _IDT_ENTRY +{ + INTERRUPT_GATE i; + // In theory we could have members for trap gates + // and task gates if we wanted. +} +IDT_ENTRY; + +// +// Defined in intsupport.asm +// +extern void HalInterruptInitializePICs(void); +extern void HalInterruptLoadIDTR(void *uLimitAndBase); +extern void HalIoDelay(void); +extern BYTE HalInterruptHandlerPreamble; +extern BYTE HalInterruptHandlerPreambleBefore; +extern BYTE HalInterruptHandlerPreambleAfter; + +// +// Defined in interrupts.c +// +extern void HalDisableInterrupts(void); +extern void HalEnableInterrupts(void); + + +// +// Defined in keyboard.c +// +extern void HalKeyboardInt(INTERRUPT_STATE *is); + +// +// Defined in timer.c +// +extern void HalTimerInt(INTERRUPT_STATE *is); + +#endif + diff --git a/kernel/hal/intsupport.asm b/kernel/hal/intsupport.asm new file mode 100644 index 0000000..3d1fcc8 --- /dev/null +++ b/kernel/hal/intsupport.asm @@ -0,0 +1,326 @@ +[BITS 32] + +FIRST_EXTERNAL_INT equ 0x30 +LAST_EXTERNAL_INT equ 0x30+15 + +GLOBAL HalInterruptLoadIDTR +GLOBAL HalInterruptGenericHandler +GLOBAL HalInterruptHandlerPreamble +GLOBAL HalInterruptHandlerPreambleBefore +GLOBAL HalInterruptHandlerPreambleAfter +GLOBAL HalInterruptInitializePICs +GLOBAL HalIoDelay + +EXTERN g_InterruptHandlerTable +EXTERN HalVideoPrint +EXTERN HalInterruptSendIrqEoi + +;; Save registers prior to calling a handler function. +;; This must be kept up to date with: +;; - Interrupt_State struct in int.h +;; - Setup_Initial_Thread_Context() in kthread.c +%macro SAVE_REGISTERS 0 + push eax + push ebx + push ecx + push edx + push esi + push edi + push ebp + push ds + push es + push fs + push gs +%endmacro + +;; Restore registers and clean up the stack after calling a handler function +;; (i.e., just before we return from the interrupt via an iret instruction). +%macro RESTORE_REGISTERS 0 + pop gs + pop fs + pop es + pop ds + pop ebp + pop edi + pop esi + pop edx + pop ecx + pop ebx + pop eax + add esp, 8 +%endmacro + +%macro INT_NO_ERROR 1 +align 8 + push 0xffffffff ; fake the error code + push %1 ; push the int number + jmp HalInterruptGenericHandler; +%endmacro + +%macro INT_WITH_ERROR 1 +align 8 + push %1 ; error code already pushed, just push int num + jmp HalInterruptGenericHandler +%endmacro + +[SECTION .text] +;;; +;;; void +;;; InterruptLoadIDTR(IDT *p) +;;; +;;; Function to load the interrupt descriptor table register (IDTR) +;;; callable from C. +;;; +HalInterruptLoadIDTR: + mov eax, [esp+4] ; p + lidt [eax] + ret + +;;; +;;; The following is code to initialize the master and slave 8259 +;;; programmable interrupt controllers (PICs). +;;; +;;; For ISA machines there are two 8259's, a master and a slave. Each has +;;; eight interrupt request (IR) pins and one interrupt (INT) pin. The +;;; master's INT pin is tied to the INT line of the microprocessor. The +;;; slave's INT pin is tied to the master's IR2 line. Thus the master's +;;; IR0, IR1, and IR3..7 are real devices. But the master's IR2 means +;;; that some IR pin on the slave is asserted. So the system IRQs are: +;;; +;;; (highest default priority) +;;; IRQ0 = master IR0 [IRQ0 = system timer] +;;; IRQ1 = master IR1 [IRQ1 = keyboard] +;;; IRQ2 = master IR2 --> IRQ8..IRQ15 = slave IR0..7 +;;; [IRQ8 = rt. timer][IRQ13 = coproc.] +;;; IRQ3 = master IR3 +;;; IRQ4 = master IR4 +;;; IRQ5 = master IR5 +;;; IRQ6 = master IR6 +;;; IRQ7 = master IR7 +;;; (lowest default priority) +;;; +;;; By default these IRQs raise the following interrupt numbers: +;;; +;;; IRQ0 = int 0x8 [collides with double exception] +;;; IRQ1 = int 0x9 [collides with coprocessor exception] +;;; IRQ2/9 = int 0x71 +;;; IRQ3 = int 0xb [collides with segment not present exception] +;;; IRQ4 = int 0xc [collides with stack fault exception] +;;; IRQ5 = int 0xd [collides with general protection exception] +;;; IRQ6 = int 0xe [collides with page fault] +;;; IRQ7 = int 0xf +;;; IRQ8 = int 0x70 +;;; IRQ10 = int 0x72 +;;; IRQ11 = int 0x73 +;;; IRQ12 = int 0x74 +;;; IRQ13 = int 0x75 +;;; IRQ14 = int 0x76 +;;; IRQ15 = int 0x77 +;;; +;;; So the problem is that IRQ0, IRQ1, IRQ3..6 all, by default, map to +;;; interrupt numbers that collide with interrupts raised by the cpu +;;; as a result of a software exception. The solution to this problem +;;; is to remap what interrupts are raised by these IRQs by reprogramming +;;; the 8259 PICs to raise new interrupt numbers. +;;; +;;; This is accomplished by sending initialization command words (ICWs) +;;; to I/O ports 0x20-0x21 (master PIC) and 0xA0-0xA1 (slave PIC). By doing +;;; so we can remap the interrupt numbers raised by IRQs so that: +;;; +;;; IRQ0 = 0x30 IRQ8 = 0x38 +;;; IRQ1 = 0x31 IRQ10 = 0x3A +;;; IRQ2/9 = 0x39 IRQ11 = 0x3B +;;; IRQ3 = 0x33 IRQ12 = 0x3C +;;; IRQ4 = 0x34 IRQ13 = 0x3D +;;; IRQ5 = 0x35 IRQ14 = 0x3E +;;; IRQ6 = 0x36 IRQ15 = 0x3F +;;; IRQ7 = 0x37 +;;; +;;; ICW1 is the same for both the master and slave PIC. Here's the meaning +;;; of the individual bits in the word: +;;; +;;; F E D C B A 9 8 | 7 6 5 4 3 2 1 0 +;;; (not used) | | | | |__ 1=expect ICW4 +;;; | | | |_____ 0=cascade 1=single +;;; | | |________ 0=interval-4, 1=8 +;;; | |___________ 0=edge triggered +;;; | 1=level triggered +;;; |______________ must be 1 +;;; +;;; +BOTH_ICW1 equ 0x11 ; expect ICW4, cascade mode, call address + ; interval=8, edge triggered mode + +;;; +;;; ICW2 tells the PICs their base interrupt number. For example, the +;;; default base interrupt for the master PIC is 0x8. Thus IRQ0=0x8, +;;; IRQ1=0x9 ... IRQ7=0xF. As stated, we want to relocate these. +;;; +MASTER_ICW2 equ 0x30 ; use ints 0x30..0x37 +SLAVE_ICW2 equ 0x38 ; use ints 0x38..0x3F + +;;; +;;; ICW3 to the master tells it which of its IR pins is connected to the +;;; slave PIC. ICW3 to the slave tells its id number. +;;; +MASTER_ICW3 equ 0x04 ; slave on IR pin 2 +SLAVE_ICW3 equ 0x02 ; slave id=2 + +;;; +;;; ICW4 to both PICs is another bitvector to set some features: +;;; +;;; +;;; F E D C B A 9 8 | 7 6 5 4 3 2 1 0 +;;; (not used) | ---- | |__ 0=MCS80/85 1=8086/88 +;;; | | |_____ 0=normal 1=auto EOI +;;; | |_________ 00=non-buf mode +;;; | 10=buf mode, slave +;;; | 11=buf mode, master +;;; |______________ 0=!nested 1=nested +;;; +BOTH_ICW4 equ 0x01 + +HalInterruptInitializePICs: + mov al, BOTH_ICW1 + out 0x20, al ; ICW1 to master PIC + call HalIoDelay + out 0xA0, al ; ICW1 to slave PIC + call HalIoDelay + + mov al, MASTER_ICW2 + out 0x21, al ; ICW2 to master PIC + call HalIoDelay + mov al, SLAVE_ICW2 + out 0xA1, al ; ICW2 to slave PIC + call HalIoDelay + + mov al, MASTER_ICW3 + out 0x21, al ; ICW3 to master PIC + call HalIoDelay + mov al, SLAVE_ICW3 + out 0xA1, al ; ICW3 to slave PIC + call HalIoDelay + + mov al, BOTH_ICW4 + out 0x21, al ; ICW4 to master PIC + call HalIoDelay + out 0xA1, al ; ICW4 to slave PIC + call HalIoDelay + +;;; +;;; After the four ICW sequence has been completed any subsequent writes +;;; to port 0x21 or 0xA1 set the IRQ mask for the master/slave PIC. If +;;; this mask is 0xFF all IRQs are disabled. If this mask is 0x00 +;;; all IRQs are enabled. +;;; + mov al, 0xff ; slave PIC cannot interrupt + out 0xA1, al + call HalIoDelay + mov al, 0xfb ; mask all IRQs but 2 (the slave PIC) in master + out 0x21, al + call HalIoDelay + ret + +;;; +;;; When doing I/O operations this routine can be used to "delay" long +;;; enough to let the external controller keep up with the cpu. +;;; +HalIoDelay: + jmp .done +.done: ret + +;;; +;;; This is the start of the main interrupt handler code. The first section +;;; of this code is called the InterruptHandlerPreamble. It consists of +;;; 256 entry points in a table (one per possible interrupt number). These +;;; preamble entry points push the interrupt number and (when needed for +;;; interrupt numbers that the processor doesn't automatically push an error +;;; code for) a fake error code. Then they jump to the main generic interrupt +;;; handling routing: InterruptGenericHandler. Thus the stack is always layed +;;; out in the same way when control passes to the generic handler: +;;; +;;; saved ss (pushed by cpu for system calls) +;;; saved esp (pushed by cpu for system calls) +;;; ------------ +;;; saved eflags (pushed by cpu) +;;; saved CS (pushed by cpu) +;;; saved IP (pushed by cpu) +;;; error code (pushed by preamble or cpu) +;;; interrupt number (pushed by preamble) +;;; esp --> return address (pushed by jmp InteruptGenericHandler) +;;; +;;; The first thing InterruptGenericHandler does is push the rest of the +;;; registers onto the stack. +;;; +align 8 +HalInterruptHandlerPreamble: +HalInterruptHandlerPreambleBefore: + INT_NO_ERROR 0 + align 8 +HalInterruptHandlerPreambleAfter: + INT_NO_ERROR 1 + INT_NO_ERROR 2 + INT_NO_ERROR 3 + INT_NO_ERROR 4 + INT_NO_ERROR 5 + INT_NO_ERROR 6 + INT_NO_ERROR 7 + INT_WITH_ERROR 8 + INT_NO_ERROR 9 + INT_WITH_ERROR 10 + INT_WITH_ERROR 11 + INT_WITH_ERROR 12 + INT_WITH_ERROR 13 + INT_WITH_ERROR 14 + INT_NO_ERROR 15 + INT_NO_ERROR 16 + INT_WITH_ERROR 17 + +;;; +;;; The rest of the interrupt numbers are INT_NO_ERRORs. Use nasm's +;;; %rep command to do them all at once. +;;; +%assign intNum 18 +%rep (256 - 18) + INT_NO_ERROR intNum +%assign intNum intNum + 1 +%endrep + +;;; +;;; This is the generic interrupt handler which is called by the preambles +;;; above. It's job is to save the registers on the stack and then transfer +;;; control to a specific per-interrupt handler. It finds the address of +;;; the per-interrupt handler to call by indexing into g_InterruptHandlerTable +;;; +align 8 +HalInterruptGenericHandler: + SAVE_REGISTERS + + ;; Ensure that we're using the kernel data segment + mov ax, (2<<3) + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ;; Get interrupt number the preamble pushed for us + mov esi, [esp+44] + + ;; Get the address of the handler function from the + ;; table of handler functions. + mov eax, g_InterruptHandlerTable + mov ebx, [eax+esi*4] + + ;; Call the handler. The argument passed is a pointer to + ;; INTERRUPT_STATE. Interrupts are enabled at this point + ;; so if the handler is non-reentrant it must disable them. + push esp + call ebx + add esp, 4 + + push esp + call HalInterruptSendIrqEoi + add esp, 4 + + RESTORE_REGISTERS + iret diff --git a/kernel/hal/io.c b/kernel/hal/io.c new file mode 100644 index 0000000..d97a211 --- /dev/null +++ b/kernel/hal/io.c @@ -0,0 +1,40 @@ +#include "kernel.h" + +// Write a byte to an I/O port. +void +out(WORD port, BYTE value ) +{ + __asm__ __volatile__ ( + "outb %b0, %w1" + : + : "a" (value), "Nd" (port) + ); +} + +// Read a byte from an I/O port. +BYTE +in(WORD port) +{ + BYTE value; + + __asm__ __volatile__ ( + "inb %w1, %b0" + : "=a" (value) + : "Nd" (port) + ); + + return value; +} + +// Short delay. May be needed when talking to some +// (slow) I/O devices. +void +iodelay(void) +{ + BYTE value = 0; + __asm__ __volatile__ ( + "outb %0, $0x80" + : + : "a" (value) + ); +} diff --git a/kernel/hal/timer.c b/kernel/hal/timer.c new file mode 100644 index 0000000..3c82691 --- /dev/null +++ b/kernel/hal/timer.c @@ -0,0 +1,43 @@ +//+---------------------------------------------------------------------------- +// +// File: timer.c +// +// Module: +// +// Synopsis: +// +// Created: sgasch 21 Oct 2003 +// +//+---------------------------------------------------------------------------- + +#include "kernel.h" +#include "hal.h" +#include "rtl.h" +#include "interrupts.h" + +void +HalTimerInt(INTERRUPT_STATE *is) +{ + static CHAR whee[] = "|/-\\\0"; + static ULONG u = 0; + static BYTE b = 0; + static unsigned int foo; + BYTE *p = (BYTE *)0x000B8000; + + out(0x61, 0x80); + u++; + + CURRENT_STAMP(&foo); + + if ((u % 500) == 0) + { + b++; + if (whee[b] == '\0') + { + b = 0; + } + *(p + 158) = whee[b]; + *(p + 159) = ' '; + } +} + diff --git a/kernel/hal/timestamp.asm b/kernel/hal/timestamp.asm new file mode 100644 index 0000000..b56bedb --- /dev/null +++ b/kernel/hal/timestamp.asm @@ -0,0 +1,19 @@ + + [BITS 32] + +GLOBAL HalReadTimestampCounter +EXTERN g_ullTimeStampCounter + +[SECTION .text] +;;; +;;; ULONGLONG +;;; HalReadTimestampCounter(void) +;;; +;;; Function to read the processor's timestamp counter +;;; +HalReadTimestampCounter: + ALIGN 4 + rdtsc + mov [g_ullTimeStampCounter], edx + mov [g_ullTimeStampCounter+4], eax + ret diff --git a/kernel/hal/video.h b/kernel/hal/video.h new file mode 100644 index 0000000..dcd7b01 --- /dev/null +++ b/kernel/hal/video.h @@ -0,0 +1,15 @@ +//+---------------------------------------------------------------------------- +// +// File: video.h +// +// Module: +// +// Synopsis: +// +// Created: sgasch 5 Jul 2003 +// +//+---------------------------------------------------------------------------- +#ifndef VIDEO_H +#define VIDEO_H + +#endif // VIDEO_H diff --git a/kernel/inc/constants.h b/kernel/inc/constants.h new file mode 100644 index 0000000..13f47bd --- /dev/null +++ b/kernel/inc/constants.h @@ -0,0 +1,50 @@ +//+---------------------------------------------------------------------------- +// +// File: constants.h +// +// Module: +// +// Synopsis: +// +// Copyright (C) 2003 +// +// Created: sgasch 10 Oct 2003 +// +//+---------------------------------------------------------------------------- + +#ifndef _CONSTANTS_H_ +#define _CONSTANTS_H_ + +// +// Constants +// +#define YES (1) +#define NO (0) +#ifndef TRUE +#define TRUE (YES) +#endif +#ifndef FALSE +#define FALSE (NO) +#endif + +#define NULL ((void *)0) + +#define ASCII_ESC (0x1B) +#define ASCII_BS (0x08) + +#define USER_PRIVILEGE (3) +#define KERNEL_PRIVILEGE (0) + +// +// Device driver constants +// +#define DEVICE_DRIVER_INITIALIZATION (1) +#define DEVICE_IO_CONTROL (2) +#define DEVICE_READ (3) +#define DEVICE_WRITE (4) +#define DEVICE_DETATCH (6) +#define DEVICE_DRIVER_SHUTDOWN (7) + +#endif /* _CONSTANTS_H_ */ + + diff --git a/kernel/inc/hal.h b/kernel/inc/hal.h new file mode 100644 index 0000000..9a52a4e --- /dev/null +++ b/kernel/inc/hal.h @@ -0,0 +1,104 @@ +//+---------------------------------------------------------------------------- +// +// File: hal.h +// +// Module: +// +// Synopsis: +// +// Created: sgasch 5 Jul 2003 +// +//+---------------------------------------------------------------------------- +#ifndef HAL_H +#define HAL_H + +// +// Video/CRT driver +// +#define BLACK (0) +#define BLUE (1) +#define GREEN (2) +#define CYAN (3) +#define RED (4) +#define MAGENTA (5) +#define YELLOW (6) +#define GRAY (7) +#define HIGH (8) +#define WHITE (HIGH | GRAY) + +void +HalVideoSetCurrentColorAttribute(BYTE bFg, BYTE bBg); + +void +HalVideoClearScreen(void); + +void +HalVideoGetCursorPosition(BYTE *pbLine, BYTE *pbCol); + +void +HalVideoSetCursorPosition(BYTE bLine, BYTE bCol); + +void +HalVideoPutNullTerminatedString(BYTE *s); + +void +HalVideoInitialize(BIOS_HARDWARE_BLOCK *phw); + +void +HalVideoPrint(CHAR *szFormat, ...); + +// +// Keyboard driver +// +#define KB_IRQ (1) +typedef WORD KEYCODE; + +typedef struct _INTERRUPT_STATE +{ + // The register contents at the time of the exception. + // We save these explicitly. + ULONG gs; + ULONG fs; + ULONG es; + ULONG ds; + ULONG ebp; + ULONG edi; + ULONG esi; + ULONG edx; + ULONG ecx; + ULONG ebx; + ULONG eax; + + // We explicitly push the interrupt number. + // This makes it easy for the handler function to determine + // which interrupt occurred. + ULONG uIntNum; + + // This may be pushed by the processor; if not, we push + // a dummy error code, so the stack layout is the same + // for every type of interrupt. + ULONG uErrorCode; + + // These are always pushed on the stack by the processor. + ULONG eip; + ULONG cs; + ULONG eflags; +} +INTERRUPT_STATE; + +typedef void (*INTERRUPT_HANDLER)(INTERRUPT_STATE *pState); + +void HalInitializeInterrupts(void); +void HalInterruptInstallHandler(ULONG uIntNum, INTERRUPT_HANDLER p); +void HalInterruptDisable(); +void HalInterruptEnable(); + +void HalIoDelay(); + +void HalTimerInt(INTERRUPT_STATE *p); +void HalKeyboardInt(INTERRUPT_STATE *p); + +extern UINT64 g_ullTimeStampCounter; +ULONG HalReadTimestampCounter(void); + +#endif // HAL_H diff --git a/kernel/inc/kernel.h b/kernel/inc/kernel.h new file mode 100644 index 0000000..7172401 --- /dev/null +++ b/kernel/inc/kernel.h @@ -0,0 +1,43 @@ +//+---------------------------------------------------------------------------- +// +// File: kernel.h +// +// Module: +// +// Synopsis: +// +// Created: sgasch 5 Jul 2003 +// +//+---------------------------------------------------------------------------- + +#ifndef _KERNEL_H_ +#define _KERNEL_H_ + +#include "types.h" +#include "constants.h" +#include "macros.h" + +// +// Shared data structures +// +typedef struct _BIOS_HARDWARE_BLOCK +{ + BYTE bDisplayType; + BYTE bFontSize; + WORD wEquipmentBitvector; + WORD wMemorySize; + WORD wSystemClock; +} +BIOS_HARDWARE_BLOCK; + +// Write a byte to an I/O port. +void +out(WORD port, BYTE value); + +BYTE +in(WORD port); + +void +iodelay(void); + +#endif /* _KERNEL_H_ */ diff --git a/kernel/inc/macros.h b/kernel/inc/macros.h new file mode 100644 index 0000000..21340ad --- /dev/null +++ b/kernel/inc/macros.h @@ -0,0 +1,43 @@ +//+---------------------------------------------------------------------------- +// +// File: macros.h +// +// Module: +// +// Synopsis: +// +// Copyright (C) 2003 +// +// Created: sgasch 10 Oct 2003 +// +//+---------------------------------------------------------------------------- + +#ifndef _MACROS_H_ +#define _MACROS_H_ + +// +// Macros +// +#define ARRAY_LENGTH(a) (sizeof((a)) / sizeof((a)[0])) +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) + +#if 1 +void _assert(CHAR *, CHAR *, ULONG); +void VideoPrint(CHAR *, ...); +#define ASSERT(x) if (x) \ + { ; } \ + else \ + { _assert(__FUNCTION__ , \ + __FILE__ , \ + __LINE__); } +#define TRACE(f, ...) VideoPrint("%s:%u> " ##f , __FUNCTION__ , \ + __LINE__ , ##__VA_ARGS__) +#else +#define ASSERT(x) ; +#define TRACE(f, ...) ; +#endif // DEBUG + +#define CURRENT_STAMP(a) asm volatile("rdtsc" : "=a"(((unsigned int *)(a))[0]), "=d"(((unsigned int *)a)[1])) + +#endif /* _MACROS_H_ */ diff --git a/kernel/inc/rtl.h b/kernel/inc/rtl.h new file mode 100644 index 0000000..3aff129 --- /dev/null +++ b/kernel/inc/rtl.h @@ -0,0 +1,33 @@ +//+---------------------------------------------------------------------------- +// +// File: rtl.h +// +// Module: +// +// Synopsis: +// +// Copyright (C) 2003 Scott Gasch +// +// Created: sgasch 5 Jul 2003 +// +//+---------------------------------------------------------------------------- + +#ifndef _RTL_H_ +#define _RTL_H_ + +void +RtlSetMemory(void *pStart, + BYTE bFill, + ULONG uLength); + +CHAR * +RtlIntToAscii(INT i, + CHAR *buf, + ULONG uBufLen, + ULONG uBase); + +#endif /* _RTL_H_ */ + + + + diff --git a/kernel/inc/types.h b/kernel/inc/types.h new file mode 100644 index 0000000..b3379e5 --- /dev/null +++ b/kernel/inc/types.h @@ -0,0 +1,80 @@ +//+---------------------------------------------------------------------------- +// +// File: types.h +// +// Module: +// +// Synopsis: +// +// Created: sgasch 10 Oct 2003 +// +//+---------------------------------------------------------------------------- + +#ifndef _TYPES_H_ +#define _TYPES_H_ + +// +// Datatype wrappers +// +#define MIN_CHAR (0x80) +#define MAX_CHAR (0x7f) +typedef char CHAR; + +#define MIN_BYTE (0x00) +#define MAX_BYTE (0xff) +typedef unsigned char BYTE; + +#define MIN_UCHAR (0x00) +#define MAX_UCHAR (0xff) +typedef unsigned char UCHAR; + +#define MIN_SHORT (0x8000) +#define MAX_SHORT (0x7fff) +typedef signed short SHORT; + +#define MIN_USHORT (0x0000) +#define MAX_USHORT (0xffff) +typedef unsigned short USHORT; + +#define MIN_WORD (0x0000) +#define MAX_WORD (0xffff) +typedef unsigned short WORD; + +#define MIN_INT (0x80000000) +#define MAX_INT (0x7fffffff) +typedef signed int INT; + +#define MIN_UINT (0x00000000) +#define MAX_UINT (0xffffffff) +typedef unsigned int UINT; + +#define MIN_LONG (0x80000000) +#define MAX_LONG (0x7fffffff) +typedef signed int LONG; + +#define MIN_ULONG (0x00000000) +#define MAX_ULONG (0xffffffff) +typedef unsigned long ULONG; + +#define MIN_INT64 (0x8000000000000000) +#define MAX_INT64 (0x7fffffffffffffff) +typedef signed long long INT64; + +#define MIN_UINT64 (0x0000000000000000) +#define MAX_UINT64 (0xffffffffffffffff) +typedef unsigned long long UINT64; + +#define MIN_BITV MIN_UINT +#define MAX_BITV MAX_UINT +typedef unsigned int BITV; + +#define MIN_BOOL MIN_UCHAR +#define MAX_BOOL MAX_UCHAR +typedef unsigned char BOOL; + +typedef unsigned int STATUS; +#define STATUS_SUCCESS (0) + +#define SIZE_T ULONG + +#endif /* _TYPES_H_ */ diff --git a/kernel/init/Makefile b/kernel/init/Makefile new file mode 100644 index 0000000..815dc69 --- /dev/null +++ b/kernel/init/Makefile @@ -0,0 +1,20 @@ +CC= gcc +CFLAGS= -Wall -imacros ../defines -I../inc +OBJS= main.o entry.o +RM= /bin/rm +NASM= nasm +NASMFLAGS= -f elf + +.SUFFIXES: .c .asm .o + +all: $(OBJS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +.asm.o: + $(NASM) $(NASMFLAGS) -o $*.o $< + +clean: + $(RM) -f $(OBJS) *~ #*# .#* + diff --git a/kernel/init/entry.asm b/kernel/init/entry.asm new file mode 100644 index 0000000..afb0434 --- /dev/null +++ b/kernel/init/entry.asm @@ -0,0 +1,23 @@ +[BITS 32] + +EXTERN KernelEntry +GLOBAL StartOfKernelImage +GLOBAL IdleLoop + +;;; +;;; I put "entry.o" first in line at the linker so I am sure this function +;;; gets put first in the kernel image. This is good because: +;;; +;;; 1. We know there is valid code at 0x00010000 when we jump here +;;; 2. We can use the address of this function as the start of image +;;; 3. We don't have to make sure main (or KernelEntry or whatever) +;;; is always the first function in main.c +;;; +StartOfKernelImage: + jmp KernelEntry + ret + +IdleLoop: + sti + hlt + jmp IdleLoop diff --git a/kernel/init/main.c b/kernel/init/main.c new file mode 100644 index 0000000..22fd14a --- /dev/null +++ b/kernel/init/main.c @@ -0,0 +1,67 @@ +//+---------------------------------------------------------------------------- +// +// File: main.c +// +// Module: +// +// Synopsis: +// +// Created: sgasch 4 Jul 2003 +// +//+---------------------------------------------------------------------------- + +#include "kernel.h" +#include "hal.h" +#include "rtl.h" + +extern char __bss_start; +extern char end; + +void +ZeroBSS(void) +{ + BYTE *pBssStart, *pBssEnd; + + pBssStart = &__bss_start; + pBssEnd = &end; + + // Fill .bss section with zero + RtlSetMemory((void *)pBssStart, '\0', (pBssEnd - pBssStart)); +} + +void +_assert(CHAR *szFunction, CHAR *szFile, ULONG uLine) +{ + HalVideoSetCursorPosition(0, 0); + HalVideoPrint("ASSERTION in %s at %s line %u.\n", + szFunction, szFile, uLine); + while(1) + { + ; + } +} + +// +// Entry point of the kernel. Not called main because I don't care to +// listen to gcc's warnings about argc and argv anymore. +// +int +KernelEntry(BIOS_HARDWARE_BLOCK *phw) +{ + ZeroBSS(); + + HalVideoInitialize(phw); + HalInitializeInterrupts(); + + HalVideoPrint("Interrupts initialized.\n"); + + hang: + HalReadTimestampCounter(); + HalVideoPrint("Timestamp Counter: %u%u\n", + ((g_ullTimeStampCounter >> 32) & 0xffffffff), + (g_ullTimeStampCounter & 0xffffffff)); + goto hang; + + return(-1); +} + diff --git a/kernel/kernel.bin b/kernel/kernel.bin new file mode 100755 index 0000000000000000000000000000000000000000..d6b13b4c3d3dfcbb09aef041ac3418f30c864680 GIT binary patch literal 13260 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 0) + { + *p++ = bFill; + uLength--; + } +} diff --git a/kernel/rtl/string.c b/kernel/rtl/string.c new file mode 100644 index 0000000..da42000 --- /dev/null +++ b/kernel/rtl/string.c @@ -0,0 +1,138 @@ +//+---------------------------------------------------------------------------- +// +// File: string.c +// +// Module: +// +// Synopsis: +// +// Copyright (C) 2003 Scott Gasch +// +// Created: sgasch 5 Jul 2003 +// +//+---------------------------------------------------------------------------- + +#include "kernel.h" + +CHAR * +strncpy(CHAR *pDest, CHAR *pSrc, SIZE_T u) +{ + char *p = pDest; + ULONG v = 0; + + while ((v < u) && (*pSrc)) + { + *pDest = *pSrc; + pDest++; pSrc++; v++; + } + + // + // If we ran out of space, null terminate the dest buffer + // + if ((*pSrc) && (u > 0)) + { + *(pDest - 1) = 0; + } + + return(p); +} + +SIZE_T +strlen(CHAR *p) +{ + ULONG i = 0; + while(*p) + { + i++; + p++; + } + return(i); +} + +CHAR * +RtlIntToAscii(INT i, + CHAR *buf, + ULONG uBufLen, + ULONG uBase) +{ + CHAR *p = (buf + uBufLen); + ULONG uSpaceLeft = uBufLen; + ULONG uMagnitude; + BOOL fNeg = FALSE; + + // + // null terminate the workspace buffer + // + if (uSpaceLeft == 0) + { + return(NULL); + } + *p = '\0'; + p--; + uSpaceLeft--; + + // + // Get the int's sign + // + if (i < 0) + { + fNeg = TRUE; + uMagnitude = -i; + } + else + { + uMagnitude = i; + } + + // + // Based on base, do the conversion. Build the string backwards from + // less significant digits. Stop if we finish or if we run out of + // uSpaceLeft. + // + switch(uBase) + { + case 10: + do + { + *p = (uMagnitude % 10) + '0'; + p--; + uSpaceLeft--; + uMagnitude /= 10; + } + while((uSpaceLeft > 0) && (uMagnitude != 0)); + break; + case 16: + do + { + *p = "0123456789ABCDEF"[uMagnitude & 15]; + p--; + uSpaceLeft--; + uMagnitude >>= 4; + } + while((uSpaceLeft > 0) && (uMagnitude != 0)); + break; + case 8: + do + { + *p = (uMagnitude & 7) + '0'; + p--; + uSpaceLeft--; + uMagnitude >>= 3; + } + while((uSpaceLeft > 0) && (uMagnitude != 0)); + break; + } + + if (TRUE == fNeg) + { + if (uSpaceLeft > 0) + { + *p = '-'; + } + } + else + { + p++; + } + return(p); +} -- 2.44.0