From 46d42d455133516f2062b5429a50323d91abd157 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Wed, 18 Oct 2023 14:04:33 -0400 Subject: [PATCH] Provided Files --- Part1/.DS_Store | Bin 6148 -> 8196 bytes Part2/.DS_Store | Bin 8196 -> 8196 bytes Part2/A4/.DS_Store | Bin 10244 -> 10244 bytes Part2/A4/01_gdb/.DS_Store | Bin 6148 -> 6148 bytes Part2/A4/03_strings/.DS_Store | Bin 0 -> 6148 bytes Part2/A4/04_scroll/.DS_Store | Bin 6148 -> 6148 bytes Part2/A4/06_irq/.DS_Store | Bin 8196 -> 8196 bytes Part2/A4/07_timers/.DS_Store | Bin 8196 -> 8196 bytes Part3/.DS_Store | Bin 0 -> 6148 bytes Part3/09_memory/Makefile | 51 ++ Part3/09_memory/README.md | 24 + Part3/09_memory/_Makefile | 52 ++ Part3/09_memory/boot/32bit_print.asm | 26 + Part3/09_memory/boot/bootsect.asm | 51 ++ Part3/09_memory/boot/disk.asm | 46 ++ Part3/09_memory/boot/gdt.asm | 35 ++ Part3/09_memory/boot/kernel_entry.asm | 4 + Part3/09_memory/boot/print.asm | 37 ++ Part3/09_memory/boot/print_hex.asm | 46 ++ Part3/09_memory/boot/switch_pm.asm | 22 + Part3/09_memory/cpu/idt.c | 16 + Part3/09_memory/cpu/idt.h | 39 ++ Part3/09_memory/cpu/interrupt.asm | 425 ++++++++++++++++ Part3/09_memory/cpu/isr.c | 153 ++++++ Part3/09_memory/cpu/isr.h | 89 ++++ Part3/09_memory/cpu/ports.c | 37 ++ Part3/09_memory/cpu/ports.h | 11 + Part3/09_memory/cpu/timer.c | 25 + Part3/09_memory/cpu/timer.h | 8 + Part3/09_memory/cpu/types.h | 35 ++ Part3/09_memory/drivers/keyboard.c | 51 ++ Part3/09_memory/drivers/keyboard.h | 3 + Part3/09_memory/drivers/screen.c | 161 ++++++ Part3/09_memory/drivers/screen.h | 23 + Part3/09_memory/kernel/kernel.c | 102 ++++ Part3/09_memory/kernel/kernel.h | 13 + Part3/09_memory/libc/function.h | 8 + Part3/09_memory/libc/globals.c | 11 + Part3/09_memory/libc/globals.h | 23 + Part3/09_memory/libc/linked.c | 480 ++++++++++++++++++ Part3/09_memory/libc/linked.h | 27 + Part3/09_memory/libc/mem.c | 76 +++ Part3/09_memory/libc/mem.h | 15 + Part3/09_memory/libc/string.c | 112 ++++ Part3/09_memory/libc/string.h | 17 + ...S3502S01_Project_III_MemoryManagement.docx | Bin 0 -> 37268 bytes 46 files changed, 2354 insertions(+) create mode 100644 Part2/A4/03_strings/.DS_Store create mode 100644 Part3/.DS_Store create mode 100644 Part3/09_memory/Makefile create mode 100644 Part3/09_memory/README.md create mode 100644 Part3/09_memory/_Makefile create mode 100644 Part3/09_memory/boot/32bit_print.asm create mode 100644 Part3/09_memory/boot/bootsect.asm create mode 100644 Part3/09_memory/boot/disk.asm create mode 100644 Part3/09_memory/boot/gdt.asm create mode 100644 Part3/09_memory/boot/kernel_entry.asm create mode 100644 Part3/09_memory/boot/print.asm create mode 100644 Part3/09_memory/boot/print_hex.asm create mode 100644 Part3/09_memory/boot/switch_pm.asm create mode 100644 Part3/09_memory/cpu/idt.c create mode 100644 Part3/09_memory/cpu/idt.h create mode 100644 Part3/09_memory/cpu/interrupt.asm create mode 100644 Part3/09_memory/cpu/isr.c create mode 100644 Part3/09_memory/cpu/isr.h create mode 100644 Part3/09_memory/cpu/ports.c create mode 100644 Part3/09_memory/cpu/ports.h create mode 100644 Part3/09_memory/cpu/timer.c create mode 100644 Part3/09_memory/cpu/timer.h create mode 100644 Part3/09_memory/cpu/types.h create mode 100644 Part3/09_memory/drivers/keyboard.c create mode 100644 Part3/09_memory/drivers/keyboard.h create mode 100644 Part3/09_memory/drivers/screen.c create mode 100644 Part3/09_memory/drivers/screen.h create mode 100644 Part3/09_memory/kernel/kernel.c create mode 100644 Part3/09_memory/kernel/kernel.h create mode 100644 Part3/09_memory/libc/function.h create mode 100644 Part3/09_memory/libc/globals.c create mode 100644 Part3/09_memory/libc/globals.h create mode 100644 Part3/09_memory/libc/linked.c create mode 100644 Part3/09_memory/libc/linked.h create mode 100644 Part3/09_memory/libc/mem.c create mode 100644 Part3/09_memory/libc/mem.h create mode 100644 Part3/09_memory/libc/string.c create mode 100644 Part3/09_memory/libc/string.h create mode 100644 Part3/CS3502S01_Project_III_MemoryManagement.docx diff --git a/Part1/.DS_Store b/Part1/.DS_Store index 9d256229599a61762dd7a2f17a670634624f02ca..569995fcbac102ff530ddef40ac2594a03914465 100644 GIT binary patch delta 173 zcmZoMXmOBWU|?W$DortDU;r^WfEYvza8E20o2aMAD7Z0TH}hr%jz7$c**Q2SHn1=X zPUd0B<4GznE=bDBPXemlF*%5}kXxd<+R)5EN5R!@ delta 115 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$jHAjU^g=(|70G4yvcro)sr=Z zB{%;QbYz-Xx??dr2ZtatP%#h)a03ZfkiLzD-b3D%+W&qdv B6m0+i diff --git a/Part2/.DS_Store b/Part2/.DS_Store index 6de918bc717d951451e1dea0d735376597bc094e..a1681092fccaec720d3a8ab4c3ad9ec3d495b674 100644 GIT binary patch delta 50 zcmZp1XmQx!EWo&Ja)e+Zr&x8hfsTTSslns}0@92flRpT`Gj?wNBf!nRnN{Kk%jW+g G?92e9P7sR# delta 26 icmZp1XmQx!EHF7#aL?wy0vzm{*(JWQZ2m36&I|yF+zF@v diff --git a/Part2/A4/.DS_Store b/Part2/A4/.DS_Store index 3f2bca80ddc3bc25d832875ff8559803b67ac4ab..5e8b2ad293eb11a31f2974d6963dfb5d168719da 100644 GIT binary patch delta 169 zcmZn(XbG6$&nU4mU^hRb#AF@;_sLa)yEgL))-Z~4F&HoyGsH6#Gn6nCF=R62F{CpT zPp%d}u838pEVw8yCqFNpfq{W>@jv#H*`~EOiu2j0`7F5S3=^oGc(D&)Bt@ zOR%1C@@`?_$#aBlCszvYn>B)qu~2NHo}wr#0|Nsi1A_pAXHI@{QcivnkT0;Ak!d+&C`gKhA&DU$ z2uqNqf#QxptoI)bfGh?EHim45RE8pkJRq5arglBD+Q~eO(u~_CYcLk7id9z|=qQ+& z8r142R9l#t0NF;ywY8iaqRRT#LGjr+xq0~=llL>qGj>kC&nU0W!Qjh~2(~AUA(J78 sAr;jgMp=YCKo4}YY-Z=+=K%T?$obAZnP0?`1L#p8Vqn-DAhLxS01Pig%m4rY delta 193 zcmZoMXfc=|#>B`mu~2NHo}wrd0|Nsi1A_nqLn=c~Qh9MfQcix-#KPs14MbQbCo@Sf zZl7GkRG1`QU2SBkqhMlWSgWH@ZDD2tVjI`ia&m|&>strKXXoVR<#z(j0vQK14-9yr zG>qy3GT0chfi@K}?f0G||CHD@5vlPk-69$gk&nXa-9_~qV?UQGwqi%v zsd$c;h5ncnIzgW&bmj;+0{=Dv{&qb|NaI&`zTao?J^Cdki|A2GQ_AU-GHBI_{(gOQ zGOteT-tZ$iDe|fwjee??RqxJP&+qx`{`=^Qo=0_D&#G}eeaF4mS{KPhJ5Ju@*?cm% z|6G@KoR`^D67n=d$kE%pO!a)MXJuMRZe$#O-|tTb8;ix(?r0cn@9iyz!Q$!e&M??{ zvb|jP{Ra;p@4uQH7hg;LO;$+?dzsihs~2*HMu)*^G%X8Veujs$*63K+l&Fg~AGHbp zWty$nTSBL`rVbmQ4k;EspYRS9ly&$p-OGH?b#|i!?0fF7-{@1^40cdEoF^UnGGO19Ls_Yd**_(qh>F|d(zAB!ku~2NHo+2a1#(>?7iw`g}F>*}iVbT_5VMt=g2f~u1^5TM|octsP z28JDzS1}c;OH@}Ini=RQ7+YG_>L^rOn3?D(m>3z?)^c))D(hPZ#b@W_=H+)yc3_s5 z>;~$Bn^YEDl$VpAmktzToNU5W%1E+#jNO|PnD;VnX6NAN06Km1M&|F#lles)IT#rj On1IGIY>p6F!wdk}0yvET delta 99 zcmZoMXfc=|#>B)qu~2NHo+2aL#(>?7jBJy6ShOe0vlcOKo9x3<$SGD`ZJ?uIVrnp% zk4>7fW3m>TJY(l(FSfmm8yj9RZD!}-=Kvb9S&-v9^JIPzM-B!cU}Rum*&HFVh8Y0; Cu^1iz diff --git a/Part2/A4/06_irq/.DS_Store b/Part2/A4/06_irq/.DS_Store index 6b62c5f26ebd8db4da641fb7619164262a1f9f5f..1561a1d1cb97f1f309146a2630497acf69ca4cdf 100644 GIT binary patch delta 171 zcmZp1XmOa}&&aTPvOFbFU(Fr@=&z5igqz%V&R s#L$$1frlZVp_oAzs5+M+ks%$Z8>?OhN1)z$5WO1<`I$GfOZ;UA07*SB?EnA( delta 42 ycmZp1XmOa}&&azmU^hP_?`9qWPsYjK0=qT~3I#Ato+_fZxtFJkc{97jUv>Z+@C`u# diff --git a/Part2/A4/07_timers/.DS_Store b/Part2/A4/07_timers/.DS_Store index 6b62c5f26ebd8db4da641fb7619164262a1f9f5f..3061e1ac577d6140acd40199d16eaa4e4ee952c0 100644 GIT binary patch delta 277 zcmZp1XmOa}&&a-s>Aexkv`w39et)k=$@&QmJN&|@$2_(9!_%1#ziBhq=t2CvxTfRFdpsQP@uxi`4SBc90p z!9VO?_PcG~e4eVT6p#W^Knh3!Dewygy!XyTSqVFy|&Sx>0a|icjG!J4AG8> j(T=(Cc6=8_S=W5c^IkY42A%n!6ZJFTy2zx!Un_6~DKHgB literal 0 HcmV?d00001 diff --git a/Part3/09_memory/Makefile b/Part3/09_memory/Makefile new file mode 100644 index 0000000..ab4485f --- /dev/null +++ b/Part3/09_memory/Makefile @@ -0,0 +1,51 @@ +C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c) +HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h) +# Nice syntax for file extension replacement +OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o} + +# Change this if your cross-compiler is somewhere else +CC = /usr/bin/gcc +GDB = /usr/bin/gdb +# -g: Use debugging symbols in gcc +CFLAGS = -g -m32 -fno-pie -ffreestanding -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs -Wall -Wextra -Werror + +# First rule is run by default +os-image.bin: boot/bootsect.bin kernel.bin + cat $^ > os-image.bin + +# '--oformat binary' deletes all symbols as a collateral, so we don't need +# to 'strip' them manually on this case +kernel.bin: boot/kernel_entry.o ${OBJ} + /usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary + +# Used for debugging purposes +kernel.elf: boot/kernel_entry.o ${OBJ} + /usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ + +run: os-image.bin + qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw + +#-s means start server at TCP:1234 +curses: os-image.bin + qemu-system-i386 -curses -s -drive file=$<,index=0,if=floppy,format=raw +# qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw -d guest_errors,int + +# Open the connection to qemu and load our kernel-object file with symbols +debug: os-image.bin kernel.elf + qemu-system-i386 -s -fda os-image.bin -d guest_errors,int & + ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf" + +# Generic rules for wildcards +# To make an object, always compile from its .c +%.o: %.c ${HEADERS} + ${CC} ${CFLAGS} -c $< -o $@ + +%.o: %.asm + nasm $< -f elf -o $@ + +%.bin: %.asm + nasm $< -f bin -o $@ + +clean: + rm -rf *.bin *.dis *.o os-image.bin *.elf + rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o libc/*.o diff --git a/Part3/09_memory/README.md b/Part3/09_memory/README.md new file mode 100644 index 0000000..da2ed0f --- /dev/null +++ b/Part3/09_memory/README.md @@ -0,0 +1,24 @@ +*Concepts you may want to Google beforehand: malloc* + +**Goal: Implement a memory allocator** + +We will add a kernel memory allocator to `libc/mem.c`. It is +implemented as a simple pointer to free memory, which keeps +growing. + +The `kmalloc()` function can be used to request an aligned page, +and it will also return the real, physical address, for later use. + +We'll change the `kernel.c` leaving all the "shell" code there, +Let's just try out the new `kmalloc()`, and check out that +our first page starts at 0x10000 (as hardcoded on `mem.c`) and +subsequent `kmalloc()`'s produce a new address which is +aligned 4096 bytes or 0x1000 from the previous one. + +Note that we added a new `strings.c:hex_to_ascii()` for +nicer printing of hex numbers. + +Another cosmetic modification is to rename `types.c` to +`type.c` for language consistency. + +The rest of the files are unchanged from last lesson. diff --git a/Part3/09_memory/_Makefile b/Part3/09_memory/_Makefile new file mode 100644 index 0000000..f4bd413 --- /dev/null +++ b/Part3/09_memory/_Makefile @@ -0,0 +1,52 @@ +C_SOURCES = $(wildcard kernel/*.c drivers/*.c cpu/*.c libc/*.c) +HEADERS = $(wildcard kernel/*.h drivers/*.h cpu/*.h libc/*.h) +# Nice syntax for file extension replacement +OBJ = ${C_SOURCES:.c=.o cpu/interrupt.o} + +# Change this if your cross-compiler is somewhere else +CC = /usr/bin/gcc +GDB = /usr/bin/gdb +# -g: Use debugging symbols in gcc +CFLAGS = -g -m32 -fno-pie -ffreestanding -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs -Wall -Wextra -Werror + +# First rule is run by default +os-image.bin: boot/bootsect.bin kernel.bin + cat $^ > os-image.bin + +# '--oformat binary' deletes all symbols as a collateral, so we don't need +# to 'strip' them manually on this case +kernel.bin: boot/kernel_entry.o ${OBJ} + /usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ --oformat binary + +# Used for debugging purposes +kernel.elf: boot/kernel_entry.o ${OBJ} + /usr/bin/ld -m elf_i386 -o $@ -Ttext 0x1000 $^ + +run: os-image.bin + qemu-system-i386 -vga std -drive file=$<,index=0,if=floppy,format=raw + +#-s means start server at TCP:1234 +curses: os-image.bin + qemu-system-i386 -curses -s -drive file=$<,index=0,if=floppy,format=raw + +# Open the connection to qemu and load our kernel-object file with symbols +debug: os-image.bin kernel.elf + qemu-system-i386 -curses -s -drive file=os-image.bin,index=0,if=floppy,format=raw -d guest_errors,int + +gdb: + ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf" + +# Generic rules for wildcards +# To make an object, always compile from its .c +%.o: %.c ${HEADERS} + ${CC} ${CFLAGS} -c $< -o $@ + +%.o: %.asm + nasm $< -f elf -o $@ + +%.bin: %.asm + nasm $< -f bin -o $@ + +clean: + rm -rf *.bin *.dis *.o os-image.bin *.elf + rm -rf kernel/*.o boot/*.bin drivers/*.o boot/*.o cpu/*.o libc/*.o diff --git a/Part3/09_memory/boot/32bit_print.asm b/Part3/09_memory/boot/32bit_print.asm new file mode 100644 index 0000000..4169a17 --- /dev/null +++ b/Part3/09_memory/boot/32bit_print.asm @@ -0,0 +1,26 @@ +[bits 32] ; using 32-bit protected mode + +; this is how constants are defined +VIDEO_MEMORY equ 0xb8000 +WHITE_OB_BLACK equ 0x0f ; the color byte for each character + +print_string_pm: + pusha + mov edx, VIDEO_MEMORY + +print_string_pm_loop: + mov al, [ebx] ; [ebx] is the address of our character + mov ah, WHITE_OB_BLACK + + cmp al, 0 ; check if end of string + je print_string_pm_done + + mov [edx], ax ; store character + attribute in video memory + add ebx, 1 ; next char + add edx, 2 ; next video memory position + + jmp print_string_pm_loop + +print_string_pm_done: + popa + ret diff --git a/Part3/09_memory/boot/bootsect.asm b/Part3/09_memory/boot/bootsect.asm new file mode 100644 index 0000000..cf6a132 --- /dev/null +++ b/Part3/09_memory/boot/bootsect.asm @@ -0,0 +1,51 @@ +; Identical to lesson 13's boot sector, but the %included files have new paths +[org 0x7c00] +KERNEL_OFFSET equ 0x1000 ; The same one we used when linking the kernel + + mov [BOOT_DRIVE], dl ; Remember that the BIOS sets us the boot drive in 'dl' on boot + mov bp, 0x9000 + mov sp, bp + + mov bx, MSG_REAL_MODE + call print + call print_nl + + call load_kernel ; read the kernel from disk + call switch_to_pm ; disable interrupts, load GDT, etc. Finally jumps to 'BEGIN_PM' + jmp $ ; Never executed + +%include "boot/print.asm" +%include "boot/print_hex.asm" +%include "boot/disk.asm" +%include "boot/gdt.asm" +%include "boot/32bit_print.asm" +%include "boot/switch_pm.asm" + +[bits 16] +load_kernel: + mov bx, MSG_LOAD_KERNEL + call print + call print_nl + + mov bx, KERNEL_OFFSET ; Read from disk and store in 0x1000 + mov dh, 32 ; Our future kernel will be larger, make this big + mov dl, [BOOT_DRIVE] + call disk_load + ret + +[bits 32] +BEGIN_PM: + mov ebx, MSG_PROT_MODE + call print_string_pm + call KERNEL_OFFSET ; Give control to the kernel + jmp $ ; Stay here when the kernel returns control to us (if ever) + + +BOOT_DRIVE db 0 ; It is a good idea to store it in memory because 'dl' may get overwritten +MSG_REAL_MODE db "Started in 16-bit Real Mode", 0 +MSG_PROT_MODE db "Landed in 32-bit Protected Mode", 0 +MSG_LOAD_KERNEL db "Loading kernel into memory", 0 + +; padding +times 510 - ($-$$) db 0 +dw 0xaa55 diff --git a/Part3/09_memory/boot/disk.asm b/Part3/09_memory/boot/disk.asm new file mode 100644 index 0000000..be3d0a9 --- /dev/null +++ b/Part3/09_memory/boot/disk.asm @@ -0,0 +1,46 @@ +; load 'dh' sectors from drive 'dl' into ES:BX +disk_load: + pusha + ; reading from disk requires setting specific values in all registers + ; so we will overwrite our input parameters from 'dx'. Let's save it + ; to the stack for later use. + push dx + + mov ah, 0x02 ; ah <- int 0x13 function. 0x02 = 'read' + mov al, dh ; al <- number of sectors to read (0x01 .. 0x80) + mov cl, 0x02 ; cl <- sector (0x01 .. 0x11) + ; 0x01 is our boot sector, 0x02 is the first 'available' sector + mov ch, 0x00 ; ch <- cylinder (0x0 .. 0x3FF, upper 2 bits in 'cl') + ; dl <- drive number. Our caller sets it as a parameter and gets it from BIOS + ; (0 = floppy, 1 = floppy2, 0x80 = hdd, 0x81 = hdd2) + mov dh, 0x00 ; dh <- head number (0x0 .. 0xF) + + ; [es:bx] <- pointer to buffer where the data will be stored + ; caller sets it up for us, and it is actually the standard location for int 13h + int 0x13 ; BIOS interrupt + jc disk_error ; if error (stored in the carry bit) + + pop dx + cmp al, dh ; BIOS also sets 'al' to the # of sectors read. Compare it. + jne sectors_error + popa + ret + + +disk_error: + mov bx, DISK_ERROR + call print + call print_nl + mov dh, ah ; ah = error code, dl = disk drive that dropped the error + call print_hex ; check out the code at http://stanislavs.org/helppc/int_13-1.html + jmp disk_loop + +sectors_error: + mov bx, SECTORS_ERROR + call print + +disk_loop: + jmp $ + +DISK_ERROR: db "Disk read error", 0 +SECTORS_ERROR: db "Incorrect number of sectors read", 0 diff --git a/Part3/09_memory/boot/gdt.asm b/Part3/09_memory/boot/gdt.asm new file mode 100644 index 0000000..e5db388 --- /dev/null +++ b/Part3/09_memory/boot/gdt.asm @@ -0,0 +1,35 @@ +gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps + ; the GDT starts with a null 8-byte + dd 0x0 ; 4 byte + dd 0x0 ; 4 byte + +; GDT for code segment. base = 0x00000000, length = 0xfffff +; for flags, refer to os-dev.pdf document, page 36 +gdt_code: + dw 0xffff ; segment length, bits 0-15 + dw 0x0 ; segment base, bits 0-15 + db 0x0 ; segment base, bits 16-23 + db 10011010b ; flags (8 bits) + db 11001111b ; flags (4 bits) + segment length, bits 16-19 + db 0x0 ; segment base, bits 24-31 + +; GDT for data segment. base and length identical to code segment +; some flags changed, again, refer to os-dev.pdf +gdt_data: + dw 0xffff + dw 0x0 + db 0x0 + db 10010010b + db 11001111b + db 0x0 + +gdt_end: + +; GDT descriptor +gdt_descriptor: + dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size + dd gdt_start ; address (32 bit) + +; define some constants for later use +CODE_SEG equ gdt_code - gdt_start +DATA_SEG equ gdt_data - gdt_start diff --git a/Part3/09_memory/boot/kernel_entry.asm b/Part3/09_memory/boot/kernel_entry.asm new file mode 100644 index 0000000..86f8fdf --- /dev/null +++ b/Part3/09_memory/boot/kernel_entry.asm @@ -0,0 +1,4 @@ +[bits 32] +[extern _start] ; Define calling point. Must have same name as kernel.c '_start' function +call _start ; Calls the C function. The linker will know where it is placed in memory +jmp $ diff --git a/Part3/09_memory/boot/print.asm b/Part3/09_memory/boot/print.asm new file mode 100644 index 0000000..b066e0d --- /dev/null +++ b/Part3/09_memory/boot/print.asm @@ -0,0 +1,37 @@ +print: + pusha + +; keep this in mind: +; while (string[i] != 0) { print string[i]; i++ } + +; the comparison for string end (null byte) +start: + mov al, [bx] ; 'bx' is the base address for the string + cmp al, 0 + je done + + ; the part where we print with the BIOS help + mov ah, 0x0e + int 0x10 ; 'al' already contains the char + + ; increment pointer and do next loop + add bx, 1 + jmp start + +done: + popa + ret + + + +print_nl: + pusha + + mov ah, 0x0e + mov al, 0x0a ; newline char + int 0x10 + mov al, 0x0d ; carriage return + int 0x10 + + popa + ret diff --git a/Part3/09_memory/boot/print_hex.asm b/Part3/09_memory/boot/print_hex.asm new file mode 100644 index 0000000..b537ef8 --- /dev/null +++ b/Part3/09_memory/boot/print_hex.asm @@ -0,0 +1,46 @@ +; receiving the data in 'dx' +; For the examples we'll assume that we're called with dx=0x1234 +print_hex: + pusha + + mov cx, 0 ; our index variable + +; Strategy: get the last char of 'dx', then convert to ASCII +; Numeric ASCII values: '0' (ASCII 0x30) to '9' (0x39), so just add 0x30 to byte N. +; For alphabetic characters A-F: 'A' (ASCII 0x41) to 'F' (0x46) we'll add 0x40 +; Then, move the ASCII byte to the correct position on the resulting string +hex_loop: + cmp cx, 4 ; loop 4 times + je end + + ; 1. convert last char of 'dx' to ascii + mov ax, dx ; we will use 'ax' as our working register + and ax, 0x000f ; 0x1234 -> 0x0004 by masking first three to zeros + add al, 0x30 ; add 0x30 to N to convert it to ASCII "N" + cmp al, 0x39 ; if > 9, add extra 8 to represent 'A' to 'F' + jle step2 + add al, 7 ; 'A' is ASCII 65 instead of 58, so 65-58=7 + +step2: + ; 2. get the correct position of the string to place our ASCII char + ; bx <- base address + string length - index of char + mov bx, HEX_OUT + 5 ; base + length + sub bx, cx ; our index variable + mov [bx], al ; copy the ASCII char on 'al' to the position pointed by 'bx' + ror dx, 4 ; 0x1234 -> 0x4123 -> 0x3412 -> 0x2341 -> 0x1234 + + ; increment index and loop + add cx, 1 + jmp hex_loop + +end: + ; prepare the parameter and call the function + ; remember that print receives parameters in 'bx' + mov bx, HEX_OUT + call print + + popa + ret + +HEX_OUT: + db '0x0000',0 ; reserve memory for our new string diff --git a/Part3/09_memory/boot/switch_pm.asm b/Part3/09_memory/boot/switch_pm.asm new file mode 100644 index 0000000..9394da3 --- /dev/null +++ b/Part3/09_memory/boot/switch_pm.asm @@ -0,0 +1,22 @@ +[bits 16] +switch_to_pm: + cli ; 1. disable interrupts + lgdt [gdt_descriptor] ; 2. load the GDT descriptor + mov eax, cr0 + or eax, 0x1 ; 3. set 32-bit mode bit in cr0 + mov cr0, eax + jmp CODE_SEG:init_pm ; 4. far jump by using a different segment + +[bits 32] +init_pm: ; we are now using 32-bit instructions + mov ax, DATA_SEG ; 5. update the segment registers + mov ds, ax + mov ss, ax + mov es, ax + mov fs, ax + mov gs, ax + + mov ebp, 0x90000 ; 6. update the stack right at the top of the free space + mov esp, ebp + + call BEGIN_PM ; 7. Call a well-known label with useful code diff --git a/Part3/09_memory/cpu/idt.c b/Part3/09_memory/cpu/idt.c new file mode 100644 index 0000000..f904a00 --- /dev/null +++ b/Part3/09_memory/cpu/idt.c @@ -0,0 +1,16 @@ +#include "idt.h" + +void set_idt_gate(int n, u32 handler) { + idt[n].low_offset = low_16(handler); + idt[n].sel = KERNEL_CS; + idt[n].always0 = 0; + idt[n].flags = 0x8E; + idt[n].high_offset = high_16(handler); +} + +void set_idt() { + idt_reg.base = (u32) &idt; + idt_reg.limit = IDT_ENTRIES * sizeof(idt_gate_t) - 1; + /* Don't make the mistake of loading &idt -- always load &idt_reg */ + __asm__ __volatile__("lidtl (%0)" : : "r" (&idt_reg)); +} diff --git a/Part3/09_memory/cpu/idt.h b/Part3/09_memory/cpu/idt.h new file mode 100644 index 0000000..27bfac5 --- /dev/null +++ b/Part3/09_memory/cpu/idt.h @@ -0,0 +1,39 @@ +#ifndef IDT_H +#define IDT_H + +#include "types.h" + +/* Segment selectors */ +#define KERNEL_CS 0x08 + +/* How every interrupt gate (handler) is defined */ +typedef struct { + u16 low_offset; /* Lower 16 bits of handler function address */ + u16 sel; /* Kernel segment selector */ + u8 always0; + /* First byte + * Bit 7: "Interrupt is present" + * Bits 6-5: Privilege level of caller (0=kernel..3=user) + * Bit 4: Set to 0 for interrupt gates + * Bits 3-0: bits 1110 = decimal 14 = "32 bit interrupt gate" */ + u8 flags; + u16 high_offset; /* Higher 16 bits of handler function address */ +} __attribute__((packed)) idt_gate_t ; + +/* A pointer to the array of interrupt handlers. + * Assembly instruction 'lidt' will read it */ +typedef struct { + u16 limit; + u32 base; +} __attribute__((packed)) idt_register_t; + +#define IDT_ENTRIES 256 +idt_gate_t idt[IDT_ENTRIES]; +idt_register_t idt_reg; + + +/* Functions implemented in idt.c */ +void set_idt_gate(int n, u32 handler); +void set_idt(); + +#endif diff --git a/Part3/09_memory/cpu/interrupt.asm b/Part3/09_memory/cpu/interrupt.asm new file mode 100644 index 0000000..bf83b7a --- /dev/null +++ b/Part3/09_memory/cpu/interrupt.asm @@ -0,0 +1,425 @@ +; Defined in isr.c +[extern isr_handler] +[extern irq_handler] + +; Common ISR code +isr_common_stub: + ; 1. Save CPU state + pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax + mov ax, ds ; Lower 16-bits of eax = ds. + push eax ; save the data segment descriptor + mov ax, 0x10 ; kernel data segment descriptor + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + + ; 2. Call C handler + call isr_handler + + ; 3. Restore state + pop eax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + popa + add esp, 8 ; Cleans up the pushed error code and pushed ISR number + sti + iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP + +; Common IRQ code. Identical to ISR code except for the 'call' +; and the 'pop ebx' +irq_common_stub: + pusha + mov ax, ds + push eax + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + call irq_handler ; Different than the ISR code + pop ebx ; Different than the ISR code + mov ds, bx + mov es, bx + mov fs, bx + mov gs, bx + popa + add esp, 8 + sti + iret + +; We don't get information about which interrupt was caller +; when the handler is run, so we will need to have a different handler +; for every interrupt. +; Furthermore, some interrupts push an error code onto the stack but others +; don't, so we will push a dummy error code for those which don't, so that +; we have a consistent stack for all of them. + +; First make the ISRs global +global isr0 +global isr1 +global isr2 +global isr3 +global isr4 +global isr5 +global isr6 +global isr7 +global isr8 +global isr9 +global isr10 +global isr11 +global isr12 +global isr13 +global isr14 +global isr15 +global isr16 +global isr17 +global isr18 +global isr19 +global isr20 +global isr21 +global isr22 +global isr23 +global isr24 +global isr25 +global isr26 +global isr27 +global isr28 +global isr29 +global isr30 +global isr31 +; IRQs +global irq0 +global irq1 +global irq2 +global irq3 +global irq4 +global irq5 +global irq6 +global irq7 +global irq8 +global irq9 +global irq10 +global irq11 +global irq12 +global irq13 +global irq14 +global irq15 + +; 0: Divide By Zero Exception +isr0: + cli + push byte 0 + push byte 0 + jmp isr_common_stub + +; 1: Debug Exception +isr1: + cli + push byte 0 + push byte 1 + jmp isr_common_stub + +; 2: Non Maskable Interrupt Exception +isr2: + cli + push byte 0 + push byte 2 + jmp isr_common_stub + +; 3: Int 3 Exception +isr3: + cli + push byte 0 + push byte 3 + jmp isr_common_stub + +; 4: INTO Exception +isr4: + cli + push byte 0 + push byte 4 + jmp isr_common_stub + +; 5: Out of Bounds Exception +isr5: + cli + push byte 0 + push byte 5 + jmp isr_common_stub + +; 6: Invalid Opcode Exception +isr6: + cli + push byte 0 + push byte 6 + jmp isr_common_stub + +; 7: Coprocessor Not Available Exception +isr7: + cli + push byte 0 + push byte 7 + jmp isr_common_stub + +; 8: Double Fault Exception (With Error Code!) +isr8: + cli + push byte 8 + jmp isr_common_stub + +; 9: Coprocessor Segment Overrun Exception +isr9: + cli + push byte 0 + push byte 9 + jmp isr_common_stub + +; 10: Bad TSS Exception (With Error Code!) +isr10: + cli + push byte 10 + jmp isr_common_stub + +; 11: Segment Not Present Exception (With Error Code!) +isr11: + cli + push byte 11 + jmp isr_common_stub + +; 12: Stack Fault Exception (With Error Code!) +isr12: + cli + push byte 12 + jmp isr_common_stub + +; 13: General Protection Fault Exception (With Error Code!) +isr13: + cli + push byte 13 + jmp isr_common_stub + +; 14: Page Fault Exception (With Error Code!) +isr14: + cli + push byte 14 + jmp isr_common_stub + +; 15: Reserved Exception +isr15: + cli + push byte 0 + push byte 15 + jmp isr_common_stub + +; 16: Floating Point Exception +isr16: + cli + push byte 0 + push byte 16 + jmp isr_common_stub + +; 17: Alignment Check Exception +isr17: + cli + push byte 0 + push byte 17 + jmp isr_common_stub + +; 18: Machine Check Exception +isr18: + cli + push byte 0 + push byte 18 + jmp isr_common_stub + +; 19: Reserved +isr19: + cli + push byte 0 + push byte 19 + jmp isr_common_stub + +; 20: Reserved +isr20: + cli + push byte 0 + push byte 20 + jmp isr_common_stub + +; 21: Reserved +isr21: + cli + push byte 0 + push byte 21 + jmp isr_common_stub + +; 22: Reserved +isr22: + cli + push byte 0 + push byte 22 + jmp isr_common_stub + +; 23: Reserved +isr23: + cli + push byte 0 + push byte 23 + jmp isr_common_stub + +; 24: Reserved +isr24: + cli + push byte 0 + push byte 24 + jmp isr_common_stub + +; 25: Reserved +isr25: + cli + push byte 0 + push byte 25 + jmp isr_common_stub + +; 26: Reserved +isr26: + cli + push byte 0 + push byte 26 + jmp isr_common_stub + +; 27: Reserved +isr27: + cli + push byte 0 + push byte 27 + jmp isr_common_stub + +; 28: Reserved +isr28: + cli + push byte 0 + push byte 28 + jmp isr_common_stub + +; 29: Reserved +isr29: + cli + push byte 0 + push byte 29 + jmp isr_common_stub + +; 30: Reserved +isr30: + cli + push byte 0 + push byte 30 + jmp isr_common_stub + +; 31: Reserved +isr31: + cli + push byte 0 + push byte 31 + jmp isr_common_stub + +; IRQ handlers +irq0: + cli + push byte 0 + push byte 32 + jmp irq_common_stub + +irq1: + cli + push byte 1 + push byte 33 + jmp irq_common_stub + +irq2: + cli + push byte 2 + push byte 34 + jmp irq_common_stub + +irq3: + cli + push byte 3 + push byte 35 + jmp irq_common_stub + +irq4: + cli + push byte 4 + push byte 36 + jmp irq_common_stub + +irq5: + cli + push byte 5 + push byte 37 + jmp irq_common_stub + +irq6: + cli + push byte 6 + push byte 38 + jmp irq_common_stub + +irq7: + cli + push byte 7 + push byte 39 + jmp irq_common_stub + +irq8: + cli + push byte 8 + push byte 40 + jmp irq_common_stub + +irq9: + cli + push byte 9 + push byte 41 + jmp irq_common_stub + +irq10: + cli + push byte 10 + push byte 42 + jmp irq_common_stub + +irq11: + cli + push byte 11 + push byte 43 + jmp irq_common_stub + +irq12: + cli + push byte 12 + push byte 44 + jmp irq_common_stub + +irq13: + cli + push byte 13 + push byte 45 + jmp irq_common_stub + +irq14: + cli + push byte 14 + push byte 46 + jmp irq_common_stub + +irq15: + cli + push byte 15 + push byte 47 + jmp irq_common_stub + diff --git a/Part3/09_memory/cpu/isr.c b/Part3/09_memory/cpu/isr.c new file mode 100644 index 0000000..bbe15b2 --- /dev/null +++ b/Part3/09_memory/cpu/isr.c @@ -0,0 +1,153 @@ +#include "isr.h" +#include "idt.h" +#include "../drivers/screen.h" +#include "../drivers/keyboard.h" +#include "../libc/string.h" +#include "timer.h" +#include "ports.h" + +isr_t interrupt_handlers[256]; + +/* Can't do this with a loop because we need the address + * of the function names */ +void isr_install() { + set_idt_gate(0, (u32)isr0); + set_idt_gate(1, (u32)isr1); + set_idt_gate(2, (u32)isr2); + set_idt_gate(3, (u32)isr3); + set_idt_gate(4, (u32)isr4); + set_idt_gate(5, (u32)isr5); + set_idt_gate(6, (u32)isr6); + set_idt_gate(7, (u32)isr7); + set_idt_gate(8, (u32)isr8); + set_idt_gate(9, (u32)isr9); + set_idt_gate(10, (u32)isr10); + set_idt_gate(11, (u32)isr11); + set_idt_gate(12, (u32)isr12); + set_idt_gate(13, (u32)isr13); + set_idt_gate(14, (u32)isr14); + set_idt_gate(15, (u32)isr15); + set_idt_gate(16, (u32)isr16); + set_idt_gate(17, (u32)isr17); + set_idt_gate(18, (u32)isr18); + set_idt_gate(19, (u32)isr19); + set_idt_gate(20, (u32)isr20); + set_idt_gate(21, (u32)isr21); + set_idt_gate(22, (u32)isr22); + set_idt_gate(23, (u32)isr23); + set_idt_gate(24, (u32)isr24); + set_idt_gate(25, (u32)isr25); + set_idt_gate(26, (u32)isr26); + set_idt_gate(27, (u32)isr27); + set_idt_gate(28, (u32)isr28); + set_idt_gate(29, (u32)isr29); + set_idt_gate(30, (u32)isr30); + set_idt_gate(31, (u32)isr31); + + // Remap the PIC + port_byte_out(0x20, 0x11); + port_byte_out(0xA0, 0x11); + port_byte_out(0x21, 0x20); + port_byte_out(0xA1, 0x28); + port_byte_out(0x21, 0x04); + port_byte_out(0xA1, 0x02); + port_byte_out(0x21, 0x01); + port_byte_out(0xA1, 0x01); + port_byte_out(0x21, 0x0); + port_byte_out(0xA1, 0x0); + + // Install the IRQs + set_idt_gate(32, (u32)irq0); + set_idt_gate(33, (u32)irq1); + set_idt_gate(34, (u32)irq2); + set_idt_gate(35, (u32)irq3); + set_idt_gate(36, (u32)irq4); + set_idt_gate(37, (u32)irq5); + set_idt_gate(38, (u32)irq6); + set_idt_gate(39, (u32)irq7); + set_idt_gate(40, (u32)irq8); + set_idt_gate(41, (u32)irq9); + set_idt_gate(42, (u32)irq10); + set_idt_gate(43, (u32)irq11); + set_idt_gate(44, (u32)irq12); + set_idt_gate(45, (u32)irq13); + set_idt_gate(46, (u32)irq14); + set_idt_gate(47, (u32)irq15); + + set_idt(); // Load with ASM +} + +/* To print the message which defines every exception */ +char *exception_messages[] = { + "Division By Zero", + "Debug", + "Non Maskable Interrupt", + "Breakpoint", + "Into Detected Overflow", + "Out of Bounds", + "Invalid Opcode", + "No Coprocessor", + + "Double Fault", + "Coprocessor Segment Overrun", + "Bad TSS", + "Segment Not Present", + "Stack Fault", + "General Protection Fault", + "Page Fault", + "Unknown Interrupt", + + "Coprocessor Fault", + "Alignment Check", + "Machine Check", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "Reserved" +}; + +void isr_handler(registers_t r) { + kprint("received interrupt: "); + char s[3]; + int_to_ascii(r.int_no, s, 3); + kprint(s); + kprint("\n"); + kprint(exception_messages[r.int_no]); + kprint("\n"); +} + +void register_interrupt_handler(u8 n, isr_t handler) { + interrupt_handlers[n] = handler; +} + +void irq_handler(registers_t r) { + /* After every interrupt we need to send an EOI to the PICs + * or they will not send another interrupt again */ + if (r.int_no >= 40) port_byte_out(0xA0, 0x20); /* slave */ + port_byte_out(0x20, 0x20); /* master */ + + /* Handle the interrupt in a more modular way */ + if (interrupt_handlers[r.int_no] != 0) { + isr_t handler = interrupt_handlers[r.int_no]; + handler(r); + } +} + +void irq_install() { + /* Enable interruptions */ + asm volatile("sti"); + /* IRQ0: timer */ + init_timer(50); + /* IRQ1: keyboard */ + init_keyboard(); +} diff --git a/Part3/09_memory/cpu/isr.h b/Part3/09_memory/cpu/isr.h new file mode 100644 index 0000000..337fbf3 --- /dev/null +++ b/Part3/09_memory/cpu/isr.h @@ -0,0 +1,89 @@ +#ifndef ISR_H +#define ISR_H + +#include "types.h" + +/* ISRs reserved for CPU exceptions */ +extern void isr0(); +extern void isr1(); +extern void isr2(); +extern void isr3(); +extern void isr4(); +extern void isr5(); +extern void isr6(); +extern void isr7(); +extern void isr8(); +extern void isr9(); +extern void isr10(); +extern void isr11(); +extern void isr12(); +extern void isr13(); +extern void isr14(); +extern void isr15(); +extern void isr16(); +extern void isr17(); +extern void isr18(); +extern void isr19(); +extern void isr20(); +extern void isr21(); +extern void isr22(); +extern void isr23(); +extern void isr24(); +extern void isr25(); +extern void isr26(); +extern void isr27(); +extern void isr28(); +extern void isr29(); +extern void isr30(); +extern void isr31(); +/* IRQ definitions */ +extern void irq0(); +extern void irq1(); +extern void irq2(); +extern void irq3(); +extern void irq4(); +extern void irq5(); +extern void irq6(); +extern void irq7(); +extern void irq8(); +extern void irq9(); +extern void irq10(); +extern void irq11(); +extern void irq12(); +extern void irq13(); +extern void irq14(); +extern void irq15(); + +#define IRQ0 32 +#define IRQ1 33 +#define IRQ2 34 +#define IRQ3 35 +#define IRQ4 36 +#define IRQ5 37 +#define IRQ6 38 +#define IRQ7 39 +#define IRQ8 40 +#define IRQ9 41 +#define IRQ10 42 +#define IRQ11 43 +#define IRQ12 44 +#define IRQ13 45 +#define IRQ14 46 +#define IRQ15 47 + +/* Struct which aggregates many registers */ +typedef struct { + u32 ds; /* Data segment selector */ + u32 edi, esi, ebp, esp, ebx, edx, ecx, eax; /* Pushed by pusha. */ + u32 int_no, err_code; /* Interrupt number and error code (if applicable) */ + u32 eip, cs, eflags, useresp, ss; /* Pushed by the processor automatically */ +} registers_t; + +void isr_install(); +void isr_handler(registers_t r); +void irq_install(); + +typedef void (*isr_t)(registers_t); +void register_interrupt_handler(u8 n, isr_t handler); + +#endif diff --git a/Part3/09_memory/cpu/ports.c b/Part3/09_memory/cpu/ports.c new file mode 100644 index 0000000..f015689 --- /dev/null +++ b/Part3/09_memory/cpu/ports.c @@ -0,0 +1,37 @@ +#include "ports.h" + +/** + * Read a byte from the specified port + */ +u8 port_byte_in (u16 port) { + u8 result; + /* Inline assembler syntax + * !! Notice how the source and destination registers are switched from NASM !! + * + * '"=a" (result)'; set '=' the C variable '(result)' to the value of register e'a'x + * '"d" (port)': map the C variable '(port)' into e'd'x register + * + * Inputs and outputs are separated by colons + */ + __asm__("in %%dx, %%al" : "=a" (result) : "d" (port)); + return result; +} + +void port_byte_out (u16 port, u8 data) { + /* Notice how here both registers are mapped to C variables and + * nothing is returned, thus, no equals '=' in the asm syntax + * However we see a comma since there are two variables in the input area + * and none in the 'return' area + */ + __asm__ __volatile__("out %%al, %%dx" : : "a" (data), "d" (port)); +} + +u16 port_word_in (u16 port) { + u16 result; + __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port)); + return result; +} + +void port_word_out (u16 port, u16 data) { + __asm__ __volatile__("out %%ax, %%dx" : : "a" (data), "d" (port)); +} diff --git a/Part3/09_memory/cpu/ports.h b/Part3/09_memory/cpu/ports.h new file mode 100644 index 0000000..0b7fa2c --- /dev/null +++ b/Part3/09_memory/cpu/ports.h @@ -0,0 +1,11 @@ +#ifndef PORTS_H +#define PORTS_H + +#include "../cpu/types.h" + +unsigned char port_byte_in (u16 port); +void port_byte_out (u16 port, u8 data); +unsigned short port_word_in (u16 port); +void port_word_out (u16 port, u16 data); + +#endif diff --git a/Part3/09_memory/cpu/timer.c b/Part3/09_memory/cpu/timer.c new file mode 100644 index 0000000..c0f4357 --- /dev/null +++ b/Part3/09_memory/cpu/timer.c @@ -0,0 +1,25 @@ +#include "timer.h" +#include "isr.h" +#include "ports.h" +#include "../libc/function.h" + +u32 tick = 0; + +static void timer_callback(registers_t regs) { + tick++; + UNUSED(regs); +} + +void init_timer(u32 freq) { + /* Install the function we just wrote */ + register_interrupt_handler(IRQ0, timer_callback); + + /* Get the PIT value: hardware clock at 1193180 Hz */ + u32 divisor = 1193180 / freq; + u8 low = (u8)(divisor & 0xFF); + u8 high = (u8)( (divisor >> 8) & 0xFF); + /* Send the command */ + port_byte_out(0x43, 0x36); /* Command port */ + port_byte_out(0x40, low); + port_byte_out(0x40, high); +} diff --git a/Part3/09_memory/cpu/timer.h b/Part3/09_memory/cpu/timer.h new file mode 100644 index 0000000..5d0195b --- /dev/null +++ b/Part3/09_memory/cpu/timer.h @@ -0,0 +1,8 @@ +#ifndef TIMER_H +#define TIMER_H + +#include "types.h" + +void init_timer(u32 freq); + +#endif diff --git a/Part3/09_memory/cpu/types.h b/Part3/09_memory/cpu/types.h new file mode 100644 index 0000000..53528fe --- /dev/null +++ b/Part3/09_memory/cpu/types.h @@ -0,0 +1,35 @@ +#ifndef TYPES_H +#define TYPES_H + +/* Instead of using 'chars' to allocate non-character bytes, + * we will use these new type with no semantic meaning */ +typedef unsigned int u32; +typedef int s32; +typedef unsigned short u16; +typedef short s16; +typedef unsigned char u8; +typedef char s8; + +#define bool u32 +#define true 1 +#define TRUE 1 +#define false 0 +#define FALSE 1 + +#define NULL ((void *) 0) +#define low_16(address) (u16)((address) & 0xFFFF) +#define high_16(address) (u16)(((address) >> 16) & 0xFFFF) + +typedef struct _node { + u32 id; // generic unique id + u32 base_register; // the memory address start of allocation + u32 limit_register; // the size in bytes of the allocation + bool ft_hole_mem; // FALSE = hole, TRUE = memory allocation + + struct _node *next; + struct _node *previous; + // current size 6 words = 24bytes +} node; +#define NODE_SIZE 24 + +#endif diff --git a/Part3/09_memory/drivers/keyboard.c b/Part3/09_memory/drivers/keyboard.c new file mode 100644 index 0000000..6f40ac6 --- /dev/null +++ b/Part3/09_memory/drivers/keyboard.c @@ -0,0 +1,51 @@ +#include "keyboard.h" +#include "../cpu/ports.h" +#include "../cpu/isr.h" +#include "screen.h" +#include "../libc/string.h" +#include "../libc/function.h" +#include "../kernel/kernel.h" + +#define BACKSPACE 0x0E +#define ENTER 0x1C + +static char key_buffer[256]; + +#define SC_MAX 57 +const char *sc_name[] = { "ERROR", "Esc", "1", "2", "3", "4", "5", "6", + "7", "8", "9", "0", "-", "=", "Backspace", "Tab", "Q", "W", "E", + "R", "T", "Y", "U", "I", "O", "P", "[", "]", "Enter", "Lctrl", + "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "`", + "LShift", "\\", "Z", "X", "C", "V", "B", "N", "M", ",", ".", + "/", "RShift", "Keypad *", "LAlt", "Spacebar"}; +const char sc_ascii[] = { '?', '?', '1', '2', '3', '4', '5', '6', + '7', '8', '9', '0', '-', '=', '?', '?', 'Q', 'W', 'E', 'R', 'T', 'Y', + 'U', 'I', 'O', 'P', '[', ']', '?', '?', 'A', 'S', 'D', 'F', 'G', + 'H', 'J', 'K', 'L', ';', '\'', '`', '?', '\\', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', ',', '.', '/', '?', '?', '?', ' '}; + +static void keyboard_callback(registers_t regs) { + /* The PIC leaves us the scancode in port 0x60 */ + u8 scancode = port_byte_in(0x60); + + if (scancode > SC_MAX) return; + if (scancode == BACKSPACE) { + backspace(key_buffer); + kprint_backspace(); + } else if (scancode == ENTER) { + kprint("\n"); + user_input(key_buffer); /* kernel-controlled function */ + key_buffer[0] = '\0'; + } else { + char letter = sc_ascii[(int)scancode]; + /* Remember that kprint only accepts char[] */ + char str[2] = {letter, '\0'}; + append(key_buffer, letter); + kprint(str); + } + UNUSED(regs); +} + +void init_keyboard() { + register_interrupt_handler(IRQ1, keyboard_callback); +} diff --git a/Part3/09_memory/drivers/keyboard.h b/Part3/09_memory/drivers/keyboard.h new file mode 100644 index 0000000..ad4fc66 --- /dev/null +++ b/Part3/09_memory/drivers/keyboard.h @@ -0,0 +1,3 @@ +#include "../cpu/types.h" + +void init_keyboard(); diff --git a/Part3/09_memory/drivers/screen.c b/Part3/09_memory/drivers/screen.c new file mode 100644 index 0000000..6f2cf14 --- /dev/null +++ b/Part3/09_memory/drivers/screen.c @@ -0,0 +1,161 @@ +#include "screen.h" +#include "../cpu/ports.h" +#include "../libc/mem.h" + +/* Declaration of private functions */ +int get_cursor_offset(); +void set_cursor_offset(int offset); +int print_char(char c, int col, int row, char attr); +int get_offset(int col, int row); +int get_offset_row(int offset); +int get_offset_col(int offset); + +/********************************************************** + * Public Kernel API functions * + **********************************************************/ + +/** + * Print a message on the specified location + * If col, row, are negative, we will use the current offset + */ +void kprint_at(char *message, int col, int row) { + /* Set cursor if col/row are negative */ + int offset; + if (col >= 0 && row >= 0) + offset = get_offset(col, row); + else { + offset = get_cursor_offset(); + row = get_offset_row(offset); + col = get_offset_col(offset); + } + + /* Loop through message and print it */ + int i = 0; + while (message[i] != 0) { + offset = print_char(message[i++], col, row, WHITE_ON_BLACK); + /* Compute row/col for next iteration */ + row = get_offset_row(offset); + col = get_offset_col(offset); + } +} + +void kprint(char *message) { + kprint_at(message, -1, -1); +} + +void kprint_hex( char *message, u32 hex, u32 digits) +{ + if( digits <= 0) { + return; + } + char hex_str[digits+1]; + hex_to_ascii( hex, hex_str, digits); + kprint( message); + kprint( hex_str); + return; +} + +void kprint_backspace() { + int offset = get_cursor_offset()-2; + int row = get_offset_row(offset); + int col = get_offset_col(offset); + print_char(0x08, col, row, WHITE_ON_BLACK); +} + + +/********************************************************** + * Private kernel functions * + **********************************************************/ + + +/** + * Innermost print function for our kernel, directly accesses the video memory + * + * If 'col' and 'row' are negative, we will print at current cursor location + * If 'attr' is zero it will use 'white on black' as default + * Returns the offset of the next character + * Sets the video cursor to the returned offset + */ +int print_char(char c, int col, int row, char attr) { + u8 *vidmem = (u8*) VIDEO_ADDRESS; + if (!attr) attr = WHITE_ON_BLACK; + + /* Error control: print a red 'E' if the coords aren't right */ + if (col >= MAX_COLS || row >= MAX_ROWS) { + vidmem[2*(MAX_COLS)*(MAX_ROWS)-2] = 'E'; + vidmem[2*(MAX_COLS)*(MAX_ROWS)-1] = RED_ON_WHITE; + return get_offset(col, row); + } + + int offset; + if (col >= 0 && row >= 0) offset = get_offset(col, row); + else offset = get_cursor_offset(); + + if (c == '\n') { + row = get_offset_row(offset); + offset = get_offset(0, row+1); + } else if (c == 0x08) { /* Backspace */ + vidmem[offset] = ' '; + vidmem[offset+1] = attr; + } else { + vidmem[offset] = c; + vidmem[offset+1] = attr; + offset += 2; + } + + /* Check if the offset is over screen size and scroll */ + if (offset >= MAX_ROWS * MAX_COLS * 2) { + int i; + for (i = 1; i < MAX_ROWS; i++) + memory_copy((u8*)(get_offset(0, i) + VIDEO_ADDRESS), + (u8*)(get_offset(0, i-1) + VIDEO_ADDRESS), + MAX_COLS * 2); + + /* Blank last line */ + char *last_line = (char*) (get_offset(0, MAX_ROWS-1) + (u8*) VIDEO_ADDRESS); + for (i = 0; i < MAX_COLS * 2; i++) last_line[i] = 0; + + offset -= 2 * MAX_COLS; + } + + set_cursor_offset(offset); + return offset; +} + +int get_cursor_offset() { + /* Use the VGA ports to get the current cursor position + * 1. Ask for high byte of the cursor offset (data 14) + * 2. Ask for low byte (data 15) + */ + port_byte_out(REG_SCREEN_CTRL, 14); + int offset = port_byte_in(REG_SCREEN_DATA) << 8; /* High byte: << 8 */ + port_byte_out(REG_SCREEN_CTRL, 15); + offset += port_byte_in(REG_SCREEN_DATA); + return offset * 2; /* Position * size of character cell */ +} + +void set_cursor_offset(int offset) { + /* Similar to get_cursor_offset, but instead of reading we write data */ + offset /= 2; + port_byte_out(REG_SCREEN_CTRL, 14); + port_byte_out(REG_SCREEN_DATA, (u8)(offset >> 8)); + port_byte_out(REG_SCREEN_CTRL, 15); + port_byte_out(REG_SCREEN_DATA, (u8)(offset & 0xff)); +} + +void clear_screen() { + int screen_size = MAX_COLS * MAX_ROWS; + int i; + u8 *screen = (u8*) VIDEO_ADDRESS; + + for (i = 0; i < screen_size; i++) { + screen[i*2] = ' '; + screen[i*2+1] = WHITE_ON_BLACK; + } + set_cursor_offset(get_offset(0, 0)); +} + + +int get_offset(int col, int row) { return 2 * (row * MAX_COLS + col); } +int get_offset_row(int offset) { return offset / (2 * MAX_COLS); } +int get_offset_col(int offset) { return (offset - (get_offset_row(offset)*2*MAX_COLS))/2; } diff --git a/Part3/09_memory/drivers/screen.h b/Part3/09_memory/drivers/screen.h new file mode 100644 index 0000000..0727d49 --- /dev/null +++ b/Part3/09_memory/drivers/screen.h @@ -0,0 +1,23 @@ +#ifndef SCREEN_H +#define SCREEN_H + +#include "../cpu/types.h" + +#define VIDEO_ADDRESS 0xb8000 +#define MAX_ROWS 25 +#define MAX_COLS 80 +#define WHITE_ON_BLACK 0x0f +#define RED_ON_WHITE 0xf4 + +/* Screen i/o ports */ +#define REG_SCREEN_CTRL 0x3d4 +#define REG_SCREEN_DATA 0x3d5 + +/* Public kernel API */ +void clear_screen(); +void kprint_at(char *message, int col, int row); +void kprint(char *message); +void kprint_backspace(); +void kprint_hex( char *message, u32 hex, u32 digits); + +#endif diff --git a/Part3/09_memory/kernel/kernel.c b/Part3/09_memory/kernel/kernel.c new file mode 100644 index 0000000..03ed3ec --- /dev/null +++ b/Part3/09_memory/kernel/kernel.c @@ -0,0 +1,102 @@ +#include "kernel.h" + +// This only runs once and from there on forth only interrupts will cause something to happen +// e.g. displaying to screen and taking input from keyboard +void _start() { +// Initialize Global Variables and set up interrrupt handlers +// These variables are a vulnerability to OS security if someone can adjust +// the lower value in the kernel code (exploiting, for example, an Intel address hack) +// than kernel memory and even kernel code can be overwritten +// https://www.wired.com/story/intel-lab-istare-hack-chips/ +// See libc/globals.h for KMEM_START and KMEM_END values +// See libc/globals.h for UMEM_START and UMEM_END values + kmem_addr = KMEM_START; + kernel_mem_limit = KMEM_END; + + umem_addr = UMEM_START; + user_mem_limit = UMEM_END; + + global_head = NULL; + global_id = 0; + + + isr_install(); + irq_install(); + + kprint("Type something, it will go through the kernel\n" + "Type HELP to list commands\n> "); +} + +// An interrupt calls this function to parse what was typed in, this is, essentially, your +// Command Line Interpreter for now. +void user_input(char *input) { + static u32 delete_id = 0; // static persistent variable for incrementing during test + static node* umem_head = NULL; // static persistent head variable for contiguous block allocations + if (strcmp(input, "END") == 0) { + kprint("Stopping the CPU. Bye!\n"); + asm volatile("hlt"); + } else if (strcmp(input, "ADD") == 0) { + umem_head = add_node( umem_head, 0x10000, 0x100, true, global_id++); + } else if (strcmp(input, "LIST") == 0) { + kprint("***** FORWARD ****\n"); + print_list( umem_head, true); + kprint("***** REVERSE ****\n"); + print_list( umem_head, false); + } else if (strcmp(input, "SHORTLIST") == 0) { + shortprint_list( umem_head, true); + kprint("\n******************\n"); + shortprint_list( umem_head, false); + } else if (strcmp(input, "PAGE") == 0) { + u32 phys_addr = 0; + u32 page = umalloc(0x4200, 0, &phys_addr); + kprint_hex( "Page: ", page, 10); + kprint_hex(", physical address: ", phys_addr, 10); + kprint("\n"); + } else if (strcmp(input, "DELETE") == 0) { + umem_head = remove_node_by_id( umem_head, delete_id++); + } else if (strcmp(input, "INSERT") == 0) { + node *new_node = create_node( 0x15000, 0x1100, true, global_id++); + node *insert_point = find_id( umem_head, 3); + umem_head = insert_node( umem_head, insert_point, new_node, true); + new_node = create_node( 0x18000, 0x2100, true, global_id++); + insert_point = find_id( umem_head, 5); + umem_head = insert_node( umem_head, insert_point, new_node, false); + } else if (strcmp(input, "SORTA") == 0) { + umem_head = hacksort_list( umem_head, true); + } else if (strcmp(input, "SORTD") == 0) { + umem_head = hacksort_list( umem_head, false); + } else if (strcmp(input, "SWAP") == 0) { + node *n1 = find_id( umem_head, 1); + node *n2 = find_id( umem_head, 5); + node *n3 = find_id( umem_head, 3); + node *n4 = find_id( umem_head, 7); + swap_node_data( n1, n2); + swap_node_data( n2, n3); + swap_node_data( n3, n4); + } else if (strcmp(input, "TEST") == 0) { + char s1[10] = "ABCDFFGH\0"; + char s2[10] = "ABCDEGH\0"; + int x = strncmp( s1, s2, 5); + kprint_hex( "STRNCMP: ", x, 16); + kprint("\n"); + + x = sstrlen( s2, 10); + kprint_hex( "SSTRLEN: ", x, 10); + kprint("\n"); + + x = strlen( s2); + kprint_hex( "STRLEN: ", x, 10); + kprint("\n"); + } else if (strcmp(input, "HELP") == 0) { + kprint("Current Commands: ADD, LIST, SHORTLIST, PAGE, DELETE,\n"); + kprint(" : END, INSERT, SORTA, SORTD, SWAP, TEST, HELP\n"); + kprint(" Review the kernel.c source code to see what each command does.\n"); + kprint(" These are hard coded and are just examples, modify as you see fit.\n"); + kprint(" for example - TEST was just added so that I could test the strlen commands.\n"); + } else { + kprint("You said: "); + kprint(input); + kprint("\n"); + } + kprint("> "); +} diff --git a/Part3/09_memory/kernel/kernel.h b/Part3/09_memory/kernel/kernel.h new file mode 100644 index 0000000..b6f309a --- /dev/null +++ b/Part3/09_memory/kernel/kernel.h @@ -0,0 +1,13 @@ +#ifndef KERNEL_H +#define KERNEL_H + +#include "../cpu/isr.h" +#include "../drivers/screen.h" +#include "../libc/string.h" +#include "../libc/globals.h" +#include "../libc/mem.h" +#include "../libc/linked.h" + +void user_input(char *input); + +#endif diff --git a/Part3/09_memory/libc/function.h b/Part3/09_memory/libc/function.h new file mode 100644 index 0000000..bf656ed --- /dev/null +++ b/Part3/09_memory/libc/function.h @@ -0,0 +1,8 @@ +#ifndef FUNCTION_H +#define FUNCTION_H + +/* Sometimes we want to keep parameters to a function for later use + * and this is a solution to avoid the 'unused parameter' compiler warning */ +#define UNUSED(x) (void)(x) + +#endif diff --git a/Part3/09_memory/libc/globals.c b/Part3/09_memory/libc/globals.c new file mode 100644 index 0000000..2fb258a --- /dev/null +++ b/Part3/09_memory/libc/globals.c @@ -0,0 +1,11 @@ +#include "globals.h" + +u32 kmem_addr; +u32 kernel_mem_limit; + +u32 umem_addr; +u32 user_mem_limit; + +u32 global_id; + +node *global_head; diff --git a/Part3/09_memory/libc/globals.h b/Part3/09_memory/libc/globals.h new file mode 100644 index 0000000..b85f77f --- /dev/null +++ b/Part3/09_memory/libc/globals.h @@ -0,0 +1,23 @@ +#ifndef GLOBAL_H +#define GLOBAL_H + +#include "../cpu/types.h" +#include "linked.h" + +// Using defines allows this file to control the ranges here rather than updating the kernel code lines +// somewhere else at various locations throughout the code +// using ALL CAPS is a conventions for defines and constants, that varies from group to group +#define KMEM_START 0x9000 // starts equal to this address for allocation +#define KMEM_END 0x10000 // cannot be equal to or more than this address +extern u32 kmem_addr; // The start value of allocation kernel datastructure allocations +extern u32 kernel_mem_limit; // the upper limit of memory allowed for kernel memory allocations + +#define UMEM_START 0x10000 // kernel memory for linked list nodes starts here +#define UMEM_END 0x40000 // kernel memory for linked list nodes must end before here +extern u32 umem_addr; // start of linked list memory allocation address +extern u32 user_mem_limit; // end of linked list memory allocation address + +extern u32 global_id; // Just an id for now but will eventually become PID + +extern node *global_head; // Kernel data structure for storing memory allocation linked list +#endif // GLOBAL_H diff --git a/Part3/09_memory/libc/linked.c b/Part3/09_memory/libc/linked.c new file mode 100644 index 0000000..e0168b9 --- /dev/null +++ b/Part3/09_memory/libc/linked.c @@ -0,0 +1,480 @@ +// linked.c ANSI C kernel environment linked list +// requires kmalloc to allocate space for linked list + +#include "linked.h" + +// Create a blank node using kmalloc, then use allocated memory to store +// a linked list node with the values passed in the parameters +// You must capture the node returned with an lval result variable +node *create_node( u32 base_register, u32 limit_register, bool ft_hole_mem, u32 id) +{ + node *new_node = NULL; + + new_node = (node *) kmalloc( NODE_SIZE, 0, (u32 *) new_node); + new_node->id = id; + new_node->base_register = base_register; + new_node->limit_register = limit_register * (new_node->id + 1); + new_node->ft_hole_mem = ft_hole_mem; + new_node->next = NULL; + new_node->previous = NULL; + + return( new_node); +} + +// Creates a node from parameters using the above and then passes it to insert the node into the linked list +// changes to *head will only affect a local stack variable +// You must capture the new head returned with an lval result variable to update head changes +// ft_hole_mem is not used currently but is included to maintain a "holes" list +// true means update memory list, false means update holes list (if you were to use it) +node *add_node( node *head, u32 base_register, u32 limit_register, bool ft_hole_mem, u32 id) +{ + node *new_node = NULL; + + new_node = create_node( base_register, limit_register, ft_hole_mem, id); + head = _add_node( head, new_node); + return( head); +} + +// place an already created node at the end of the list +// changes to *head will only affect a local stack variable +// You must capture the new head returned with an lval result variable to update head changes +node *_add_node( node *head, node *new_node) +{ + node *iterator = NULL; + node *previous = NULL; + + if( new_node == NULL) { + return(head); + } + if( head == NULL) { + head = new_node; + return ( head ); + } // Stop here is head is empty and just set to new and return new head. + + iterator = head->next; // Head is not NULL so continue with next node as iterator + while( iterator != NULL) { + previous = iterator; + iterator = iterator->next; + } // until iterator is null keep pointing new node back to previous node and iterator to next node + + if( previous == NULL) { // just head existed, append new node ... + new_node->previous = head; + head->next = new_node; + } else { // iterated to end of list, append new node ... + new_node->previous = previous; + previous->next = new_node; + } // ... and return + return( head); +} + +// Iterate to last node and list and then append new node to it +node *append_node( node *head, node* new_node) +{ + node *iterator = head; + + if( new_node == NULL) { + return( head); + } + if( head == NULL) { + return( new_node); + } + while( iterator->next != NULL) { // step until at last node + iterator = iterator->next; + } + iterator->next = new_node; + new_node->previous = iterator; + new_node->next = NULL; + return(head); +} + +// Given an input id search list until id found +// return lval of that *node value otherwise return a NULL value +node *find_id( node *head, u32 id) +{ + if( head == NULL) { + return( head); + } + node *iterator = head; + + while(( iterator != NULL) && ( iterator->id != id)) { // search and compare id + iterator = iterator->next; + } + return( iterator); +} + +// Given an id, find it with the function above and then use node* value (target) +// to create a gap of previous and next nodes which is then closed. +// if id is not found nothing happens +node *remove_node_by_id( node *head, u32 id) +{ + node *target = NULL; + + target = find_id( head, id); + if( target == NULL) { + return( head); + } + head = zip_list( head, target); + free_node( target); + return( head); +} + +// Given a specific lval node* target value, remove it from the list *head +// and then close the gap created by removing target. +node *zip_list( node *head, node* target) +{ + bool has_previous = true; + bool has_next = true; + + if( target == NULL) { + return( head); + } + if( target->previous == NULL) { + has_previous = false; +#ifdef KDEBUG + kprint("has_previous FALSE\n"); +#endif + } + if ( target->next == NULL) { + has_next = false; +#ifdef KDEBUG + kprint("has_next FALSE\n"); +#endif + } + if( (has_next == true) && (has_previous == true)) { // Node in middle of chain + target->previous->next = target->next; + target->next->previous = target->previous; + } else if ( (has_next == false && has_previous == true)) { // Node at end of chain + target->previous->next = NULL; + } else if ( (has_next == true && has_previous == false)) { // Node at start of chain + target->next->previous = NULL; + head = head->next; + } else { // ( has_next == false && has_previous == false) // Node is isolated + head = NULL; + } + return(head); +} + +// Use find_id to get the insert_point +// decide if insertion is before or after +// if ft_before_after = FALSE then insert before target +// if ft_before_after = TRUE then append after target +node *insert_node( node *head, node* insert_point, node *new_node, bool ft_before_after) +{ + if( new_node == NULL) { // Nothing to add so return unchanged + return( head); + } + if( insert_point == NULL) { // If no point specified either place before head or after tail + if( ft_before_after) { // if after is set append as new tail + head = append_node( head, new_node); + } else { // if before than insert before head as new head + new_node->next = head; + new_node->previous = NULL; + head-> previous = new_node; + return( new_node); // return new head + } + return( head); + } + + if( ft_before_after) { // if adding after then... + if( insert_point->next != NULL) { // insert node after insertion_point + insert_point->next->previous = new_node; + new_node->next = insert_point->next; + } else { // insertion point is last node instead, skip above operations + new_node->next = NULL; + } + new_node->previous = insert_point; + insert_point->next = new_node; + } else { + if( insert_point->previous != NULL) { // insert node before insertion point + insert_point->previous->next = new_node; + new_node->previous = insert_point->previous; + } else { // insertion point is head + new_node->previous = NULL; + head = new_node; // set new head as insertion point + } + insert_point->previous = new_node; + new_node->next = insert_point; + } + return(head); // return changes (if any) to head +} + +node *get_tail( node *head) +{ + if( head == NULL) { + return(head); + } + node *tail = head; + while( tail->next != NULL) { // iterate until the next node is not NULL + tail = tail->next; + } + return( tail); +} + +void get_min_max_id( node *head, u32 *min, u32 *max) +{ + if( head == NULL) { + return; + } + node *iterator = head; + *min = 0xFFFFFFFF; + *max = 0; + + while( iterator != NULL) { // iterate until the next node is not NULL + if( *min > iterator->id) { + *min = iterator->id; + } + if( *max < iterator->id) { + *max = iterator->id; + } + iterator = iterator->next; + } + return; +} + +void swap_node_data( node* left, node *right) +{ + if(( left == NULL) || (right == NULL)) { + kprint("Invalid element.\n"); + return; + } + node swap; + + kprint_hex("SWAP ", left->id, 4); + kprint_hex(" with ", right->id, 4); + kprint("\n"); + + swap.id = left->id; + swap.base_register = left->base_register; + swap.limit_register = left->limit_register; + swap.ft_hole_mem = left->ft_hole_mem; + + left->id = right->id; + left->base_register = right->base_register; + left->limit_register = right->limit_register; + left->ft_hole_mem = right->ft_hole_mem; + + right->id = swap.id; + right->base_register = swap.base_register; + right->limit_register = swap.limit_register; + right->ft_hole_mem = swap.ft_hole_mem; + + return; +} + +node *swap_nodes( node *head, node** left, node **right) +{ + + if( head == NULL) { // don't swap nodes in a list that is empty + return( head); + } + + if( (*left == NULL) || (*right == NULL) ) { // if either or both nodes are NULL do nothing + return( head); + } + if ( head == *left) { // if left happens to be head than set head to right + head = *right; + } + if ( head == *right) { // if right happens to be head then set head to left + head = *left; + } + + node *left_previous = (*left)->previous; + node *left_next = (*left)->next; + node *right_previous = (*right)->previous; + node *right_next = (*right)->next; + + if( (*left)->previous != NULL) { // make sure not to access a NULL value node + (*left)->previous->next = *right; // previous node to left now points to right instead of left + } + + if( (*left)->next != NULL) { // same + (*left)->next->previous = *right; // next node to left now points to right instead of left + } + + if( (*right)->previous != NULL) { // same + (*right)->previous->next = *left; // same as above but right previous to left now + } + if( (*right)->next != NULL) { // same + (*right)->next->previous = *left; // same as above... + } + + (*left)->previous = right_previous; // this will obliterate left->previous so it is preserved in left_previous + (*left)->next = right_next; // this will obliterate left->next so it is preserved in left_next + + (*right)->previous = left_previous; // now point right node back to previous to left node (obliterated above) + (*right)->next = left_next; // now point right node forward to next of left node (obliterated above) + + node* swap; + swap = *left; + *left = *right; + *right = swap; + +#ifdef KDEBUG + kprint("LEFT: "); + print_node( *left); + kprint(" RIGHT: "); + print_node( *right); + kprint("\n"); +#endif + + return( head); // return new head if it changed +} + +// good ways to sort a list, transfer it to an array and merge or quicksort it +// not so good bubble-sort, insertion, select-sort, heap-sort +// worst, this ugly hack +node *hacksort_list( node* head, bool ft_descending_ascending) +{ + if( head == NULL) { + kprint("LIST EMPTY\n"); + return(head); + } + if( ft_descending_ascending) { + kprint("ASCENDING\n"); + } else { + kprint("DESCENDING\n"); + } + node* outer_iterator = head; + node* inner_iterator = head->next; + node* max = head; + + while( outer_iterator != NULL) { + max = outer_iterator; + inner_iterator = outer_iterator->next; + while( inner_iterator != NULL) { + if( ft_descending_ascending) { + if( max->id > inner_iterator->id) { // This decides ASCENDING + max = inner_iterator; + } + } else { + if( max->id < inner_iterator->id) { // This decides SORT DESCENDING + max = inner_iterator; + } + } + inner_iterator = inner_iterator->next; +/******* STOP COUNTER for Infnite Loop Halting + u32 count = 0; + if( count++ > 10) { + return(head); + } +****/ + } + if( ( outer_iterator != NULL) && ( outer_iterator != max)) { + swap_node_data( outer_iterator, max); + } + outer_iterator = outer_iterator->next; + } + return( head); +} + +node *mergesort_list( node* head, node *pivot, node *left, node *right) +{ + return( head); // disabled, not yet implemented + if( ( head == NULL) || ( pivot == NULL) | ( left == NULL) | ( right == NULL)) { + return( head); + } + node *start = left; + node *end = right; + while( (start->next != NULL) || (start->id != end->id)) { + if( start->id < pivot->id) { + ; + } + } + + return( head); +} + +// stand in for delete, this only zeroes all the values of the node allocated by kmalloc +// the memory is not reclaimed, need to add a tracker for these kmalloc allocated blocks +// to implement a proper delete function +void free_node( node *target) +{ + memory_set( (u8 *) target, 0, NODE_SIZE); + target = NULL; + return; +} + +// Print a single node's values +void print_node( node *current) +{ + char numstr[16]; + + if( current->previous != NULL) { + kprint_hex( "(", current->previous->id, 4); + kprint( ")<-"); + } else { + kprint("NULL<-"); + } + + memory_set( (u8 *) numstr, 0, 16); + hex_to_ascii( current->id, numstr, 16); + kprint("ID: "); kprint( numstr); kprint(" --- "); + memory_set( (u8 *) numstr, 0, 16); + hex_to_ascii( current->base_register, numstr, 16); + kprint("BASE: "); kprint( numstr); kprint(" --- "); + memory_set( (u8 *) numstr, 0, 16); + hex_to_ascii( current->limit_register, numstr, 16); + kprint("LIMIT: "); kprint( numstr); kprint(" --- "); + if( current->ft_hole_mem) { + kprint("MEMORY "); + } else { + kprint("HOLE "); + } + if( current->next != NULL) { + kprint_hex( "->", current->next->id, 4); + } else { + kprint("->NULL"); + } + kprint("\n"); + return; +} + +// just print the id separated by commas with a line-feed at the end +void shortprint_list( node *head, bool ft_descending_ascending) +{ + if( head == NULL) { + kprint( "EMPTY."); + kprint("\n"); + return; + } + node *iterator = NULL; + if( ft_descending_ascending) { + iterator = head; + while( iterator != NULL) { + kprint_hex( ",",iterator->id, 4); + iterator = iterator->next; + } + } else { + iterator = get_tail( head); + while( iterator != NULL) { + kprint_hex( ",",iterator->id, 4); + iterator = iterator->previous; + } + } + kprint("\n"); + return; +} + +// iterate through a list printing out the values of each node +void print_list( node *head, bool ft_descending_ascending) +{ + if( head == NULL) { + kprint( "EMPTY."); + return; + } + node *iterator = NULL; + if( ft_descending_ascending) { + iterator = head; + while( iterator != NULL) { + print_node( iterator); + iterator = iterator->next; + } + } else { + iterator = get_tail( head); + while( iterator != NULL) { + print_node( iterator); + iterator = iterator->previous; + } + } + return; +} diff --git a/Part3/09_memory/libc/linked.h b/Part3/09_memory/libc/linked.h new file mode 100644 index 0000000..a44fea2 --- /dev/null +++ b/Part3/09_memory/libc/linked.h @@ -0,0 +1,27 @@ +#ifndef LINKED_H +#define LINKED_H + +#include "../cpu/types.h" +#include "globals.h" +#include "mem.h" + +node *create_node( u32 base_register, u32 limit_register, bool ft_hole_mem, u32 id); +node *add_node( node *head, u32 base_register, u32 limit_register, bool ft_hole_mem, u32 id); +node *_add_node( node *head, node *new_node); +node *append_node( node *head, node* new_node); +node *find_id( node *head, u32 id); +node *remove_node_by_id( node *head, u32 id); +node *zip_list( node *head, node* target); +node *insert_node( node *head, node* insert_point, node *new_node, bool ft_before_after); +node *get_tail( node *head); +void get_min_max_id( node *head, u32 *min, u32 *max); +node *swap_nodes( node *head, node **left, node **right); +void swap_node_data( node* left, node *right); +node *hacksort_list( node* head, bool ft_backward_forward); +node *mergesort_list( node* head, node *pivot, node *left, node *right); +void free_node( node *target); +void print_node( node *current); +void print_list( node *head, bool ft_backward_forward); +void shortprint_list( node *head, bool ft_backward_forward); + +#endif diff --git a/Part3/09_memory/libc/mem.c b/Part3/09_memory/libc/mem.c new file mode 100644 index 0000000..042bb43 --- /dev/null +++ b/Part3/09_memory/libc/mem.c @@ -0,0 +1,76 @@ +#include "mem.h" + +void memory_copy(u8 *source, u8 *dest, int nbytes) { + int i; + for (i = 0; i < nbytes; i++) { + *(dest + i) = *(source + i); + } +} + +void memory_set(u8 *dest, u8 val, u32 len) { + u8 *temp = (u8 *)dest; + for ( ; len != 0; len--) *temp++ = val; +} + +// See globals.h for kmem_addr global declaration +// kmem_addr is reserved block of memory for kernel to store linked list nodes +/* Implementation is just a pointer to some free memory which + * keeps growing */ +u32 kmalloc(u32 size, int align, u32 *phys_addr) { + /* Pages are aligned to 4K, or 0x1000 */ + if (align == 1 && (kmem_addr & 0xFFFFF000)) { + kmem_addr &= 0xFFFFF000; + kmem_addr += 0x1000; + } + + + char c[16]; + hex_to_ascii( *phys_addr, c, 16); + kprint("Inside kmalloc phys_addr (before) = "); + kprint(c); + kprint(" --- "); + + /* Save the physical address */ + *phys_addr = kmem_addr; + + hex_to_ascii( *phys_addr, c, 16); + kprint("(after) phys_addr = "); + kprint(c); + kprint("\n"); + + u32 ret = kmem_addr; + kmem_addr += size; /* Remember to increment the pointer */ + return ret; +} + + +// See globals.h for user_start global declaration +// umem_addr is reserved block of memory for kernel to store linked list nodes +/* Implementation is just a pointer to some free memory which + * keeps growing */ +u32 umalloc(u32 size, int align, u32 *phys_addr) { + /* Pages are aligned to 4K, or 0x1000 */ + if (align == 1 && (umem_addr & 0xFFFFF000)) { + umem_addr &= 0xFFFFF000; + umem_addr += 0x1000; + } + + + char c[16]; + hex_to_ascii( *phys_addr, c, 16); + kprint("Inside umalloc phys_addr (before) = "); + kprint(c); + kprint(" --- "); + + /* Save the physical address */ + *phys_addr = umem_addr; + + hex_to_ascii( *phys_addr, c, 16); + kprint("(after) phys_addr = "); + kprint(c); + kprint("\n"); + + u32 ret = umem_addr; + umem_addr += size; /* Remember to increment the pointer */ + return ret; +} diff --git a/Part3/09_memory/libc/mem.h b/Part3/09_memory/libc/mem.h new file mode 100644 index 0000000..5c1b9d7 --- /dev/null +++ b/Part3/09_memory/libc/mem.h @@ -0,0 +1,15 @@ +#ifndef MEM_H +#define MEM_H + +#include "../cpu/types.h" +#include "../libc/string.h" +#include "../drivers/screen.h" +#include "globals.h" + +void memory_copy(u8 *source, u8 *dest, int nbytes); +void memory_set(u8 *dest, u8 val, u32 len); + +u32 kmalloc(u32 size, int align, u32 *phys_addr); +u32 umalloc(u32 size, int align, u32 *phys_addr); + +#endif diff --git a/Part3/09_memory/libc/string.c b/Part3/09_memory/libc/string.c new file mode 100644 index 0000000..3dea5e0 --- /dev/null +++ b/Part3/09_memory/libc/string.c @@ -0,0 +1,112 @@ +#include "string.h" + +/** + * K&R implementation + */ +void int_to_ascii(int n, char str[], int size) { + memory_set( (u8 *) str, 0, size); + int i, sign; + if ((sign = n) < 0) n = -n; + i = 0; + do { + str[i++] = n % 10 + '0'; + } while ((n /= 10) > 0); + + if (sign < 0) str[i++] = '-'; + str[i] = '\0'; + + reverse(str); +} + +// Convert the numeric value n to a character string of 0x#...# where # is hexadecimal values +// Size is the size of the char str[] array so number of hex digits is 1 less than size +// because you need a terminating 0 +// the char str[] is erased with 0 values at start. +void hex_to_ascii(int n, char str[], int size) { + memory_set( (u8 *) str, 0, size); + append(str, '0'); + append(str, 'x'); + char zeros = 0; + + s32 tmp; + int i; + + for (i = 28; i > 0; i -= 4) { + tmp = (n >> i) & 0xF; + if (tmp == 0 && zeros == 0) continue; + zeros = 1; + if (tmp >= 0xA) append(str, tmp - 0xA + 'A'); + else append(str, tmp + '0'); + } + + tmp = n & 0xF; + if (tmp >= 0xA) append(str, tmp - 0xA + 'A'); + else append(str, tmp + '0'); +} + +/* K&R */ +void reverse(char s[]) { + int c, i, j; + for (i = 0, j = strlen(s)-1; i < j; i++, j--) { + c = s[i]; + s[i] = s[j]; + s[j] = c; + } +} + +/* K&R */ +int strlen(char s[]) { + int i = 0; + while (s[i] != '\0') ++i; + return i; +} + +// not K&R +int sstrlen( char s[], u32 size) +{ + if( size <= 0) { + return(0); + } + u32 i = 0; + while ( (s[i] != '\0') && ( i < size)) { ++i; } + return( i); +} + +void append(char s[], char n) { + int len = strlen(s); + s[len] = n; + s[len+1] = '\0'; +} + +void backspace(char s[]) { + int len = strlen(s); + s[len-1] = '\0'; +} + +/* K&R + * Returns <0 if s10 if s1>s2 */ +int strcmp(char s1[], char s2[]) { + int i; + for (i = 0; s1[i] == s2[i]; i++) { + if (s1[i] == '\0') return 0; + } + return s1[i] - s2[i]; +} + +// Not K&R +// Compare strings for first size characters +// if size > both string sizes compare whole string +int strncmp( char s1[], char s2[], u32 size) +{ + u32 i = 0; + while( (s1[i] != '\0') && (s2[i] != '\0') && (i < size) && (s1[i] == s2[i])) { + ++i; + } + if(i >= size) { // match exceeded size, they are same up to size chars + return(0); + } else if ( (s1[i] == s2[i])) { // match ended before size are they same? (includeing NULL matches) + return(0); + } // else don't match return diff + return( s1[i] - s2[i]); +} + diff --git a/Part3/09_memory/libc/string.h b/Part3/09_memory/libc/string.h new file mode 100644 index 0000000..b10db2c --- /dev/null +++ b/Part3/09_memory/libc/string.h @@ -0,0 +1,17 @@ +#ifndef STRINGS_H +#define STRINGS_H + +#include "../cpu/types.h" +#include "mem.h" + +void int_to_ascii(int n, char str[], int size); +void hex_to_ascii(int n, char str[], int size); +void reverse(char s[]); +int strlen(char s[]); +void backspace(char s[]); +void append(char s[], char n); +int strcmp(char s1[], char s2[]); +int strncmp(char s1[], char s2[], u32 size); +int sstrlen( char s1[], u32 size); + +#endif diff --git a/Part3/CS3502S01_Project_III_MemoryManagement.docx b/Part3/CS3502S01_Project_III_MemoryManagement.docx new file mode 100644 index 0000000000000000000000000000000000000000..77df99eddaa359de0b90aebe55e3e1562aff178e GIT binary patch literal 37268 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 E0Dev`UH||9 literal 0 HcmV?d00001