diff --git a/fs/command.ppc b/fs/command.ppc new file mode 100644 index 0000000..80e0be4 --- /dev/null +++ b/fs/command.ppc @@ -0,0 +1,1655 @@ +# 1 "command.k" +# 1 "" +# 1 "" +# 31 "" +# 1 "/usr/include/stdc-predef.h" 1 3 4 +# 32 "" 2 +# 1 "command.k" +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +cmdstart: + jmp start + +# 1 "crt/crt.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; Limits +; +# 31 "crt/crt.k" +; +; Magic numbers +; + + + + +; +; CRT librairies +; + +# 1 "./crt/sys.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; SHUTDOWN syscall +; +; End virtual machine +; +Sys.Shutdown := 0x00 + +; +; EXIT syscall +; +; Return to COMMAND.COM +; +Sys.Exit := 0x01 + +; +; EXEC syscall +; +; IN +; ax0 = new RIP to load +; ax1 = frame ID to switch to +; +; New frame ID must be higher than current +; frame ID, and cannot be below 5 +; +; New CR1 is set so that new RIP corresponds to 1MB +; +Sys.ExecuteInFrame := 0x02 + +; FIND syscalls +; +; Find file on disk +; +; IN +; ax0 = address of name buffer +; ax1 = size of name buffer +; +; OUT +; rax = # of bytes written in name buffer +; rdx = size of file +; +; +Sys.FindFirst := 0x20 +Sys.FindNext := 0x21 + +; +; OPEN syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = handle of file, or <0 if couldn't open +; +Sys.OpenFile := 0x30 + +; +; CREATE syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = 0 on success, or <0 if couldn't open +; +Sys.CreateFile := 0x31 + +; +; REMOVE syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = 0 on success, or <0 if couldn't open +; +Sys.RemoveFile := 0x32 + +; +; CLOSE syscall +; +; IN +; ax0 = file handle +; +Sys.CloseFile := 0x35 + +; +; READ syscall +; +; IN +; ax0 = file handle +; ax1 = buffer address +; ax2 = buffer size +; +; OUT +; rax = number of bytes read, <0 on error +; +Sys.ReadFile := 0x38 + +; Halt mode +Sys.EnterHaltMode := 0x999 +# 43 "crt/crt.k" 2 +# 1 "./crt/str.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int strnlen(char *, int) +; +strnlen: + mov rcx, ax1 + scasb ax0, zero + + sub rax, ax1, rcx + ret + +; +; int strlen(char *) +; +strlen: + mov rcx, 0x7AFFFFFF + mov rdx, rcx + scasb ax0, zero + + sub rax, rdx, rcx + ret + +; +; void strcpy(char *, const char *) +; +strcpy: +.l: + mov rcx, b[ax1] + mov b[ax0], rcx + + jrcxz .r + + inc ax0 + inc ax1 + + jmp .l + +.r: + ret + +; +; void strncpy(char *, const char *, int) +; +strncpy: + mov rcx, ax2 + jrcxz .r + +.l: + mov b[ax0], b[ax1] + + inc ax0 + inc ax1 + + loop .l + +.r: + ret + +; +; void strnzcpy(char *, const char *, int) +; +strnzcpy: + mov rcx, ax2 + jrcxz .r + +.l: + mov rax, b[ax1] + mov b[ax0], rax + + jraxz .r + + inc ax0 + inc ax1 + + loop .l + +.z: + nul b[ax0] + +.r: + ret + +; +; int strcmp(const char *str1, const char *str2) +; +; Returns: +; 0 if the contents of both strings are equal +; >0 if the first character that does not match has a greater value in str1 than in str2 +; <0 if the first character that does not match has a lower value in str1 than in str2 +; +strcmp: + nul rsi +.l: + movzx rax, b[ax0+rsi] + movzx rdx, b[ax1+rsi] + + bne rax, rdx, .r + + ; both zero? + add rcx, rax, rdx + jrcxz .r + + inc rsi + jmp .l + +.r: + sub rax, rdx + ret + +; +; int strncmp(const char *str1, const char *str2, int maxn) +; +strncmp: + mov rcx, ax2 + jrcxz .r + +.l: + movzx rax, b[ax0] + movzx rdx, b[ax1] + + bne rax, rdx, .r + + inc ax0 + inc ax1 + loop .l + +.r: + sub rax, rdx + ret + +; +; char *strchrnul(const char *str, int ch) +; +strchrnul: + mov rcx, 0x7AFFFFFF + scasb ax0, ax1 + + mov rax, ax0 + ret + +; +; char *strchr(const char *str, int ch) +; +strchr: + mov rcx, 0x7AFFFFFF + scasb ax0, ax1 + + bnz b[ax0], .r + nul rax + ret + +.r: + mov rax, ax0 + ret + +; +; void strrev(char *buf, const char *str) +; +; buf and src must NOT overlap +; +strrev: + bzr b[ax1], .z + + ; save str's location + mov rsi, ax1 + + ; go to str's end, just before + ; the null terminator + mov rcx, 0x7AFFFFFF + scasb ax1, zero + dec ax1 + +.l: + ; copy, going backward though str + ; and forward through buf + mov b[ax0], b[ax1] + + beq ax1, rsi, .r + + inc ax0 + dec ax1 + jmp .l + +.r: + nul b[ax0+1] + ret + +.z: + nul b[ax0] + ret + +; +; void strrev2(char *str) +; +; Inverses str +; +strrev2: + bzr b[ax0], .r + + mov ax1, ax0 + + ; go to str's end, just before + ; the null terminator + mov rcx, 0x7AFFFFFF + scasb ax1, zero + dec ax1 + + ; increase ax0 while decreasing ax1, performing exchanges +.l: + blteu ax1, ax0, .r + + xchg b[ax0], b[ax1] + + inc ax0 + dec ax1 + jmp .l + +.r: + ret +# 44 "crt/crt.k" 2 +# 1 "./crt/mem.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; void memcpy(void *, const void *, int) +; +memcpy: + mov rcx, ax2 + jrcxz .r + +.l: + sub rdx, ax2, rcx + mov b[ax0+rdx], b[ax1+rdx] + loop .l + +.r: + ret + +; +; void memzero(void *, int) +; +memzero: + mov rcx, ax1 + jrcxz .r + +.l: + nul b[ax0] + inc ax0 + loop .l + +.r: + ret +# 45 "crt/crt.k" 2 +# 1 "./crt/time.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; struct TIME +; { +; byte sec; +0 (0-59) +; byte min; +1 (0-59) +; byte hour; +2 (0-23) +; byte mday; +3 (0-31) +; byte month; +4 (0-11) +; byte; +5 (pad) +; word year; +6 (0-65536) +; word yday; +8 (0-365) +; word; +10 (pad) +; dword; +12 (pad) +; } 16 bytes +; + +; +; int DaysInYear(int year) +; +DaysInYear: + mov rax, 365 + + ; divisible by 4? + rem rcx, ax0, 4 + jrcxnz .end + + ; divisible by 100? + rem rcx, ax0, 100 + jrcxnz .leap + + ; divisible by 400? + rem rcx, ax0, 400 + jrcxnz .end + +.leap: + inc rax, 1 + +.end: + ret + +; +; TIME *GetTimeUTC(void) +; +GetTimeUTC: + ytime + + mov rdi, .buf + + ; seconds + rem rsi, rax, 60 + mov b[rdi], rsi + + ; minutes + div rsi, rax, 60 + rem rsi, 60 + mov b[rdi+1], rsi + + ; hours + div rsi, rax, 3600 + rem rsi, 24 + mov b[rdi+2], rsi + + ; month days + div rsi, rax, 3600*24 + mov b[rdi+3], rsi + + ; month + mov b[rdi+4], rbx + + ; years + mov w[rdi+6], rcx + + ; + ; ydays (TODO) + ; + + mov rax, .buf + ret + +.buf = [24] +# 46 "crt/crt.k" 2 + +# 1 "./crt/fmt/ltostr.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; void itoa(char *buf, int num, int base) +; +itoa: + mov ax3, 1 + jmp ltostr + +; +; void utoa(char *buf, int num, int base) +; +utoa: + nul ax3 + jmp ltostr + +; +; void ltostr(char *buf, int num, int base, bool signed) +; +ltostr: + mov rax, ax0 + nul rcx + + ; make sure base is in [2, 32] + bltu ax2, 2, .bad + bltu 36, ax2, .bad + + ; deal with zero + bzr ax1, .is_zero + + ; deal with base 10 signedness + + bzr ax3, .conv + bne ax2, 10, .conv ; base 10 + + shr rcx, ax1, 63 ; extract ax1 sign + jrcxz .conv + + neg ax1 ; NEG if negative + + ; main loop +.conv: + bzr ax1, .fini + + rem rdx, ax1, ax2 ; ax1 % base + + blt 9, rdx, .nondec ; rdx > 9 ? + + inc rdx, '0' + jmp .next + +.nondec: + inc rdx, 55 ; 'A' - 10 + +.next: + mov b[ax0], rdx + + inc ax0 + + div ax1, ax2 + jmp .conv + + ; add minus flag, null-terminate and reverse +.fini: + jrcxz .cxz + mov b[ax0], '-' + inc ax0 + +.cxz: + nul b[ax0] + + call strrev2, rax + ret + +; +; exceptional cases +; +.bad: + mov b[ax0], 0 + ret + +.is_zero: + mov b[ax0], 48 ; '0' + mov b[ax0+1], 0 + ret +# 48 "crt/crt.k" 2 +# 1 "./crt/fmt/strtol.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int strtol(const char *str, int base) +; +; rax = integer extracted from str +; rdx = pointer to first invalid byte +; +strtol: + mov ax2, 1 + jmp strtoq + +; +; int strtoul(const char *str, int base) +; +; rax = integer extracted from str +; rdx = pointer to first invalid byte +; +strtoul: + nul ax2 + jmp strtoq + +; +; int strtoq(const char *str, int base, bool signed) +; +; guesses base when 'base'=0 +; +strtoq: + nul rax, rsi + mov rdx, ax0 + + ; make sure base is in [2, 32] + beq ax1, 1, .bad + bltu 36, ax1, .bad + + ; empty string? + bzr b[rdx], .done + +.skip_spc: + bne b[rdx], ' ', .no_spc + inc rdx + jmp .skip_spc + +.no_spc: + ; skip + + bne b[rdx], '+', .no_plus + inc rdx + +.no_plus: + ; unsigned? + bzr ax2, .unsigned + + ; parse '-' + bne b[rdx], '-', .unsigned + inc rdx + mov rsi, 1 + +.unsigned: + ; base 0 + bzr ax1, .base_0 + + ; base prefix? + bne b[rdx], '0', .main_loop + + inc rdx + movzx rcx, b[rdx] + + ; "0x"/"0b" prefix + jrcxz .done ; "0" + beq rcx, 'x', .parsed_0x + beq rcx, 'b', .parsed_0b + + ; may be octal, but we don't care + ; we accept "0110101010" (despite base=2) for instance + jmp .main_loop + +.parsed_0x: + ; are we in base 16? + ; if not, leave rax = 0 and *rdx = 'x' + bne ax1, 16, .done + ; else + inc rdx, 1 + jmp .main_loop + +.parsed_0b: + ; are we in base 2? + ; if not, leave rax = 0 and *rdx = 'b' + bne ax1, 2, .done + ; else + inc rdx + jmp .main_loop + +.base_0: + ; guess base + + beq b[rdx], '0', .b0_not10 + + ; must be base 10 + mov ax1, 10 + jmp .main_loop + +.b0_not10: + inc rdx + + bne b[rdx], 'x', .b0_not16 + inc rdx + mov ax1, 16 + jmp .main_loop + +.b0_not16: + bne b[rdx], 'b', .b0_not2 + inc rdx + mov ax1, 2 + jmp .main_loop + +.b0_not2: + ; take octal by default + mov ax1, 8 + +.main_loop: + movzx rcx, b[rdx] + inc rdx + + ; between 0 and 9? + bltu rcx, '0', .done + bltu '9', rcx, .not_digit10 + + ; yes + sub rcx, '0' + jmp .next + +.not_digit10: + bltu rcx, 'A', .done + bltu 'Z', rcx, .not_digitAZ + + sub rcx, 55 ; 'A' - 10 + jmp .next + +.not_digitAZ: + bltu rcx, 'a', .done + bltu 'z', rcx, .done + + sub rcx, 87 ; 'a' - 10 + jmp .next + +.next: + ; too large for base? + blteu ax1, rcx, .done + + mul rax, ax1 + inc rax, rcx + jmp .main_loop + +.done: + ; negative? + bzr rsi, .r + + ; yes + neg rax + +.r: + ret + +.bad: + ret +# 49 "crt/crt.k" 2 +# 1 "./crt/fmt/doprnt.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; typedef int (*PUTC)(int ch) +; int doprnt(PUTC putc, int n, const char *fmt, va_list ap) +; +doprnt: + push rbp + mov rbp, rsp + + push r12, r13, r14 + push r15, r16, r17 + + mov r12, ax2 ; fmt + mov r14, ax3 ; va_list + mov r15, ax1 ; n + mov r17, ax0 ; putc + nul r16 ; return value + +.main_loop: + ; find '%' or null-terminator + mov rcx, 0x7AFFFFFF + mov r13, r12 + scasb r13, '%' + + ; everything below r13 is a regular character; print it +.print_regular: + beq r12, r13, .check_modf + + call .doput, b[r12] + + inc r12, 1 + jmp .print_regular + +.check_modf: + ; did we find a '%' ? + ; if not, then we found fmt's null-terminator; we're done + bne b[r12], '%', .epilogue + + ; we did find a modifier / '%' + mov rax, b[r12+1] + inc r12, 2 + + beq rax, 's', .modf_s + beq rax, 'c', .modf_c + beq rax, 'p', .modf_p + beq rax, 'x', .modf_x + beq rax, 'd', .modf_d + beq rax, 'o', .modf_o + beq rax, 'b', .modf_b + beq rax, '%', .modf_percent + + ; unrecognized + jmp .bad_modifier + +.modf_s: + ; get string address + mov r13, q[r14] + inc r14, 8 + + bzr r13, .nullstring + +.print_string: + movzx ax0, b[r13] + bzr ax0, .main_loop + + inc r13 + call .doput + + jmp .print_string + +.modf_c: + call .doput, q[r14] + inc r14, 8 + + jmp .main_loop + +.modf_p: + call .doput, '0' + call .doput, 'x' + ; Fallthrough + +.modf_x: + mov ax2, 16 + jmp .print_number + +.modf_d: + mov ax2, 10 + jmp .print_number + +.modf_o: + mov ax2, 8 + jmp .print_number + +.modf_b: + mov ax2, 2 + jmp .print_number + +.print_number: + ; allocate itoa conversion buffer + sub rsp, 80 + mov r13, rsp + + ; assume modifier already set up ax2 + call itoa, rsp, q[r14] + inc r14, 8 + +.print_itoa_buf: + movzx ax0, b[r13] + + bzr ax0, .pib_end_loop + inc r13 + + call .doput + jmp .print_itoa_buf + +.pib_end_loop: + inc rsp, 80 + jmp .main_loop + +.modf_percent: + call .doput, '%' + jmp .main_loop + +.bad_modifier: + ; print "%?" to clearly indicate that something is wrong + call .doput, '%' + call .doput, '?' + + jmp .main_loop + +.nullstring: + ; %s was passed a NULL + call .doput, '(' + call .doput, 'n' + call .doput, 'u' + call .doput, 'l' + call .doput, 'l' + call .doput, ')' + + jmp .main_loop + +.epilogue: + mov rax, r16 + + pop r17, r16 + pop r15, r14 + pop r13, r12 + + leave + ret +; +; prints ax0 +; +.doput: + ; update print count + inc r16 + + ; if n==0, don't print + ; we follow the C convention that sprintf()-like functions + ; should return the number of characters that would have + ; been printed/written if 'n' were big enough + bzr r15, .r + + ; decrement n and print + dec r15 + call r17 + + ; did putc fail? + jraxz .r + + ; yes, so artificially set n=0 + nul r15 + +.r: + ret +# 50 "crt/crt.k" 2 +# 1 "./crt/fmt/printf.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int putc(int ch) +; +putc: + prn ax0 + nul rax + ret + +; +; int printf(const char *fmt, ...) +; +printf: + mov ax2, ax0 + mov ax0, putc + mov ax1, 0x7AFFFFFF + add ax3, rsp, 8 + jmp doprnt + +; +; int nprintf(const char *fmt, int n, ...) +; +nprintf: + mov ax2, ax0 + mov ax0, putc + add ax3, rsp, 8 + jmp doprnt + +; +; Print a string +; Guaranteed to only affect rcx and ax0 +; +print: +.l: + movzx rax, b[ax0] + jraxz .r + + prn rax + inc ax0 + + jmp .l + +.r: + ret + +; +; Print exactly ax1 characters +; +nprint: + mov rcx, ax1 + jrcxz .r + +.l: + prn b[ax0] + inc ax0 + loop .l +.r: + ret +# 51 "crt/crt.k" 2 + +exit: + mov rax, Sys.Exit + trap 0 + +abort: + crash +# 8 "command.k" 2 + + + + +; COMMAND.COM guarantees that programs +; are always loaded on a 16KB boundary +; This is guaranteed to be the case +; in all future versions as well + + +start: + mov rsp, 0x104000 ; + 16KB + mov rbp, zero + + call main + + mov rax, Sys.EnterHaltMode + trap 0 + + crash + +cmd.versionstr = "COMMAND.COM, version 0.1 (KVISC)\nCopyright (C) 2019, The OS/K Team\nMIT license (permissive), see LICENCE file in source tree" + +argbuf.size := 256 +argbuf = [argbuf.size] +argv0 = [argbuf.size] +argv1pos = 0 + +stdin_echoing = 1 + +prompt = [32] + +main: + mov rsi, prompt + mov b[rsi+0], 'C' + mov b[rsi+1], ':' + mov b[rsi+2], '\' + mov b[rsi+3], '>' + mov b[rsi+4], ' ' + +.print_prompt: + call print, prompt + + ; empty stuff + call memzero, argbuf, argbuf.size + call memzero, argv0, argbuf.size + nul q[argv1pos] + + ; call nprint, argv0, argbuf.size + + ; iterator through argbuf + nul rcx + +.input_loop: + pause + pause + pause + + ; Fill .buf with user input + scan rax + jraxz .input_loop + + ; ESC key pressed? + beq rax, 0x1B, .handle_EXIT + + ; backspace character? + bne rax, 8, .handle_input + + ; anything to delete? + jrcxz .input_loop ; no + + ; yes, delete it + dec rcx + add rdx, rcx, argbuf + nul b[rdx] + + ; update screen + bzr b[stdin_echoing], .input_loop + prn 8 + + jmp .input_loop + +.handle_input: + bzr b[stdin_echoing], .se.z + prn rax + +.se.z: + beq rax, 10, .extract_argv0 + + ; when max line length is reached, + ; force a newline + beq rcx, argbuf.size, .extract_argv0 + + ; add character to buffer and increase iterator (rcx) + add rdx, rcx, argbuf + mov b[rdx], rax + inc rcx + + ; another one + jmp .input_loop + +.extract_argv0: + ; did we read anything at all? + ; if not, just go back to waiting input + jrcxz .print_prompt + + ; find first whitespace or null-terminator + mov rcx, argbuf.size + mov rdx, argbuf + scasb rdx, ' ' + + ; argv1 exists? if so, save its position + mov rsi, rdx + +.next_space: + mov rcx, b[rsi] + jrcxz .do_extract + + ; skip spaces + bne rcx, ' ', .not_a_space + inc rsi + jmp .next_space + +.not_a_space: + ; if we're here, we found a + ; non-zero non-space character + mov q[argv1pos], rsi + + ; fallthrough + +.do_extract: + ; how much do we copy? + sub rcx, rdx, argbuf + jrcxz .detect_builtin + dec rcx + + mov rdi, argbuf + mov rsi, argv0 + +.copy_loop: + mov b[rsi], b[rdi] + + inc rdi + inc rsi + + loop .copy_loop + +.detect_builtin: + +.builtin_cls = "cls" + call strcmp, argv0, .builtin_cls + jraxz .handle_CLS + +.builtin_crash = "crash" + call strcmp, argv0, .builtin_crash + jraxz .handle_CRASH + +.builtin_date = "date" + call strcmp, argv0, .builtin_date + jraxz .handle_DATE + +.builtin_dir = "dir" + call strcmp, argv0, .builtin_dir + jraxz .handle_DIR + +.builtin_dump = "dump" + call strcmp, argv0, .builtin_dump + jraxz .handle_DUMP + +.builtin_echo = "echo" + call strcmp, argv0, .builtin_echo + jraxz .handle_ECHO + +.builtin_erase = "erase" + call strcmp, argv0, .builtin_erase + jraxz .handle_ERASE + +.builtin_exit = "exit" + call strcmp, argv0, .builtin_exit + jraxz .handle_EXIT + +.builtin_help = "help" + call strcmp, argv0, .builtin_help + jraxz .handle_HELP + +.builtin_halt = "halt" + call strcmp, argv0, .builtin_halt + jraxz .handle_HALT + +.builtin_make = "make" + call strcmp, argv0, .builtin_make + jraxz .handle_MAKE + +.builtin_print = "print" + call strcmp, argv0, .builtin_print + jraxz .handle_PRINT + +.builtin_prompt = "prompt" + call strcmp, argv0, .builtin_prompt + jraxz .handle_PROMPT + +.builtin_remove = "remove" + call strcmp, argv0, .builtin_remove + jraxz .handle_REMOVE + +.builtin_time = "time" + call strcmp, argv0, .builtin_time + jraxz .handle_TIME + +.builtin_vers = "vers" + call strcmp, argv0, .builtin_vers + jraxz .handle_VERS + + jmp .try_exec + +; +; call builtins +; + +# 1 "usr/cmd-dir.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +N := 11 + +.handle_DIR: + push rbp + mov rbp, rsp + + push r12, r13 + push r14, r15 + + nul r12 ; no. of files found + nul r13 ; no. of directories found + nul r14 ; total amount of bytes found + + call print, .dirmsg + +.first: + mov rax, Sys.FindFirst + mov ax0, .buf + mov ax1, 0x80 + trap 0 + + jmp .list + +.next: + mov rax, Sys.FindNext + mov ax0, .buf + mov ax1, 0x80 + trap 0 + +.list: + jraxz .end + + mov r15, rcx ; file size + inc r14, rcx + + ; directory? + bnz rdx, .is_dir + + ; found a file + inc r12 + + ; separate extension from file name + mov rcx, 0x80 + mov rsi, .buf + mov rdi, rsi + scasb rsi, '.' + + ; print file name + sub ax1, rsi, rdi + dec ax1 + call nprint, rdi + + ; calculate where to put extension + sub rdi, rsi, .buf + dec rdi + +.ext_pad: + ; print at least N non-space characters before extension + blte N, rdi, .print_ext + prn ' ' + inc rdi + jmp .ext_pad + +.print_ext: + prn ' ' + ; here we print at least 4 characters excluding '.' + mov rcx, 4 + + bne b[rsi], '.', .print_ext.1 + inc rsi + +.print_ext.1: + bzr b[rsi], .print_ext.2 + + ; print and decrease rcx, unless it's already 0 + prn b[rsi] + inc rsi + jrcxz .print_ext.1 + + dec rcx + jmp .print_ext.1 + +.print_ext.2: + ; did we print at least 4 bytes? + jrcxz .print_bytes ; yes, carry on + +.pe2.l: + prn ' ' + loop .pe2.l + +.print_bytes: + ; print file size in bytes + prn ' ' + prn ' ' + prn ' ' + + shr rax, r15, 10 + and r15, 1023 + + push r15, rax + call printf, .bytesstr + inc rsp, 16 + +.prepare_next: + ; go find next entry + prn 10 + jmp .next + +.end: + shr rax, r14, 10 + shr rdx, rax, 10 + and rax, 1023 + and r14, 1023 + + push r14, rax, rdx + call printf, .endstr0 + inc rsp, 24 + + push r13, r12 + call printf, .endstr1 + inc rsp, 16 + + pop r15, r14 + pop r13, r12 + + leave + jmp .print_prompt + + ; special case: direcory +.is_dir: + inc r13 + + ; use printf instead of print + ; because it returns # of printed + ; characters + call printf, .buf + + blte N, rax, .dir_no_pad + sub rcx, N, rax + dec rcx + +.dir.l: + prn ' ' + loop .dir.l + +.dir_no_pad: + call print, .dir_ext + + jmp .print_bytes + +.buf = [0x80] +.dir_ext = " " +.endstr0 = " total %dMB + %dKB + %dB\n" +.endstr1 = " found %d file(s), %d dir(s)\n" +.dirmsg = "Directory of C:\\\n\n" + +.bytesstr = "%d kilobytes + %d bytes" +; .bytesstr = "%dMB + %dKB + %dB" # too soon +# 228 "command.k" 2 +# 1 "usr/cmd-exec.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +.try_exec: + ; try without appending ".com" + mov rax, Sys.OpenFile + mov ax0, argv0 + trap 0 + + ; we good? + blte zero, rax, .do_exec + + ; nope, append ".com" and try again + sub rcx, argbuf.size, 5 + scasb ax0, zero ; find null-term + mov b[ax0+0], '.' ; ".com" + mov b[ax0+1], 'c' + mov b[ax0+2], 'o' + mov b[ax0+3], 'm' + nul b[ax0+4] + + ; try again + mov rax, Sys.OpenFile + mov ax0, argv0 + trap 0 + + ; still no good? + bltz rax, .exec_not_found + +.do_exec: + ; load file into memory + mov ax0, rax + mov ax1, 0x108000 ; + 32KB + mov ax2, 0x8000 + mov rax, Sys.ReadFile + trap 0 + + ; save load address + mov rcx, rax + + ; close file + mov rax, Sys.CloseFile + trap 0 + + ; read anything? + bltz rcx, .couldnt_read + jrcxz .empty_file + + ; all good, let's go + mov rax, Sys.ExecuteInFrame + mov ax0, 0x108000 ; + 32KB + mov ax1, 5 + trap 0 + + ; unreachable + jmp abort +# 229 "command.k" 2 +# 1 "usr/cmd-fsmisc.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +.handle_ERASE: + mov rax, Sys.RemoveFile + mov ax0, q[argv1pos] + bzr ax0, .need_params + trap 0 + + bltz rax, .couldnt_remove + + jmp .handle_MAKE ; re-create it back + +.handle_MAKE: + mov rax, Sys.CreateFile + mov ax0, q[argv1pos] + bzr ax0, .need_params + trap 0 + + bltz rax, .couldnt_open + + jmp .print_prompt + +.handle_PRINT: + mov rax, Sys.OpenFile + mov ax0, q[argv1pos] + bzr ax0, .need_params + trap 0 + + bltz rax, .file_not_found + + mov ax0, rax + mov ax1, 0x108000 ; + 32KB + mov ax2, 0x8000 + + mov rax, Sys.ReadFile + trap 0 + + mov rcx, rax + + mov rax, Sys.CloseFile + trap 0 + + bltz rcx, .couldnt_read + jrcxz .empty_file + + call nprint, 0x108000 ; + 32KB, rcx + + jmp .print_prompt + +.handle_REMOVE: + mov rax, Sys.RemoveFile + mov ax0, q[argv1pos] + bzr ax0, .need_params + trap 0 + + bltz rax, .couldnt_remove + + jmp .print_prompt +# 230 "command.k" 2 + +.handle_CLS: + prn 0x8BF00001 + jmp .print_prompt + +.handle_CRASH: + jmp abort + +.handle_DATE: + call GetTimeUTC + + mov rcx, b[rax+4] + inc rcx + + push b[rax+3], rcx, w[rax+6] + call printf, .datefmt + inc rsp, 40 + + jmp .print_prompt + +.datefmt = "%d/%d/%d\n" + +.handle_DUMP: + dump + jmp .print_prompt + +.handle_ECHO: + mov rax, q[argv1pos] + jraxz .echo.end + + call print, rax + +.echo.end: + prn 10 + jmp .print_prompt + +.handle_EXIT: + mov rax, Sys.Shutdown + trap 0 + jmp .print_prompt + +.handle_HALT: + mov rax, Sys.EnterHaltMode + trap 0 + jmp .print_prompt + +.handle_PROMPT: + mov ax0, prompt + mov ax1, q[argv1pos] + bzr ax1, .need_params + call strcpy + + jmp .print_prompt + +.handle_TIME: + call GetTimeUTC + + push b[rax], b[rax+1], b[rax+2] + call printf, .timefmt + inc rsp, 24 + + jmp .print_prompt + +.timefmt = "%d:%d:%d\n" + +.handle_VERS: + call print, cmd.versionstr + prn 10 + + jmp .print_prompt + +.handle_HELP: + call print, .helpmsg + call print, .helpmsg.cls + call print, .helpmsg.date + call print, .helpmsg.dir + call print, .helpmsg.dump + call print, .helpmsg.echo + call print, .helpmsg.erase + call print, .helpmsg.exit + call print, .helpmsg.help + call print, .helpmsg.halt + call print, .helpmsg.make + call print, .helpmsg.print + call print, .helpmsg.prompt + call print, .helpmsg.remove + call print, .helpmsg.time + call print, .helpmsg.ver + + jmp .print_prompt + +.helpmsg = "The following commands are built-in:\n" +.helpmsg.cls = " CLS Clear screen\n" +.helpmsg.date = " DATE Display current date\n" +.helpmsg.dir = " DIR Print contents of current directory\n" +.helpmsg.dump = " DUMP Toggles debug instruction dumping\n" +.helpmsg.echo = " ECHO Write arguments to standard output\n" +.helpmsg.erase = " ERASE Clear a file, making it blank\n" +.helpmsg.exit = " EXIT Initiate machine shutdown\n" +.helpmsg.help = " HELP Display these messages\n" +.helpmsg.halt = " HALT Put processor in halt mode\n" +.helpmsg.make = " MAKE Create an empty file\n" +.helpmsg.print = " PRINT Display contents of text file\n" +.helpmsg.prompt = " PROMPT Change the command line prompt\n" +.helpmsg.remove = " REMOVE Delete a file (permanently)\n" +.helpmsg.time = " TIME Display current time of day\n" +.helpmsg.ver = " VERS Display current COMMAND.COM version\n" + +.exec_not_found: + push argv0 + call printf, .enf_errmsg + inc rsp, 8 + + jmp .print_prompt + +.enf_errmsg = "%s: file not found\n" + +.file_not_found: + push q[argv1pos], argv0 + call printf, .fnf_errmsg + inc rsp, 16 + + jmp .print_prompt + +.fnf_errmsg = "%s: %s: file not found\n" + +.empty_file: + push q[argv1pos], argv0 + call printf, .ef_errmsg + inc rsp, 16 + + jmp .print_prompt + +.ef_errmsg = "%s: %s: file was empty\n" + +.couldnt_open: + push q[argv1pos], argv0 + call printf, .cno_errmsg + inc rsp, 16 + + jmp .print_prompt + +.cno_errmsg = "%s: %s: an error occured while opening file\n" + +.couldnt_remove: + push q[argv1pos], argv0 + call printf, .cne_errmsg + inc rsp, 16 + + jmp .print_prompt + +.cne_errmsg = "%s: %s: an error occured while removing file\n" + +.couldnt_read: + push q[argv1pos], argv0 + call printf, .cnr_errmsg + inc rsp, 16 + + jmp .print_prompt + +.cnr_errmsg = "%s: %s: an error occured while reading file\n" + +.need_params: + call print, argv0 + call print, .np_errmsg + + jmp .print_prompt + +.np_errmsg = ": need more parameters\n" diff --git a/fs/doskrnl.ppc b/fs/doskrnl.ppc new file mode 100644 index 0000000..c737d80 --- /dev/null +++ b/fs/doskrnl.ppc @@ -0,0 +1,1629 @@ +# 1 "doskrnl.k" +# 1 "" +# 1 "" +# 31 "" +# 1 "/usr/include/stdc-predef.h" 1 3 4 +# 32 "" 2 +# 1 "doskrnl.k" +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +__sysmain: + jmp start + +# 1 "crt/crt.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; Limits +; +# 31 "crt/crt.k" +; +; Magic numbers +; + + + + +; +; CRT librairies +; + +# 1 "./crt/sys.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; SHUTDOWN syscall +; +; End virtual machine +; +Sys.Shutdown := 0x00 + +; +; EXIT syscall +; +; Return to COMMAND.COM +; +Sys.Exit := 0x01 + +; +; EXEC syscall +; +; IN +; ax0 = new RIP to load +; ax1 = frame ID to switch to +; +; New frame ID must be higher than current +; frame ID, and cannot be below 5 +; +; New CR1 is set so that new RIP corresponds to 1MB +; +Sys.ExecuteInFrame := 0x02 + +; FIND syscalls +; +; Find file on disk +; +; IN +; ax0 = address of name buffer +; ax1 = size of name buffer +; +; OUT +; rax = # of bytes written in name buffer +; rdx = size of file +; +; +Sys.FindFirst := 0x20 +Sys.FindNext := 0x21 + +; +; OPEN syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = handle of file, or <0 if couldn't open +; +Sys.OpenFile := 0x30 + +; +; CREATE syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = 0 on success, or <0 if couldn't open +; +Sys.CreateFile := 0x31 + +; +; REMOVE syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = 0 on success, or <0 if couldn't open +; +Sys.RemoveFile := 0x32 + +; +; CLOSE syscall +; +; IN +; ax0 = file handle +; +Sys.CloseFile := 0x35 + +; +; READ syscall +; +; IN +; ax0 = file handle +; ax1 = buffer address +; ax2 = buffer size +; +; OUT +; rax = number of bytes read, <0 on error +; +Sys.ReadFile := 0x38 + +; Halt mode +Sys.EnterHaltMode := 0x999 +# 43 "crt/crt.k" 2 +# 1 "./crt/str.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int strnlen(char *, int) +; +strnlen: + mov rcx, ax1 + scasb ax0, zero + + sub rax, ax1, rcx + ret + +; +; int strlen(char *) +; +strlen: + mov rcx, 0x7AFFFFFF + mov rdx, rcx + scasb ax0, zero + + sub rax, rdx, rcx + ret + +; +; void strcpy(char *, const char *) +; +strcpy: +.l: + mov rcx, b[ax1] + mov b[ax0], rcx + + jrcxz .r + + inc ax0 + inc ax1 + + jmp .l + +.r: + ret + +; +; void strncpy(char *, const char *, int) +; +strncpy: + mov rcx, ax2 + jrcxz .r + +.l: + mov b[ax0], b[ax1] + + inc ax0 + inc ax1 + + loop .l + +.r: + ret + +; +; void strnzcpy(char *, const char *, int) +; +strnzcpy: + mov rcx, ax2 + jrcxz .r + +.l: + mov rax, b[ax1] + mov b[ax0], rax + + jraxz .r + + inc ax0 + inc ax1 + + loop .l + +.z: + nul b[ax0] + +.r: + ret + +; +; int strcmp(const char *str1, const char *str2) +; +; Returns: +; 0 if the contents of both strings are equal +; >0 if the first character that does not match has a greater value in str1 than in str2 +; <0 if the first character that does not match has a lower value in str1 than in str2 +; +strcmp: + nul rsi +.l: + movzx rax, b[ax0+rsi] + movzx rdx, b[ax1+rsi] + + bne rax, rdx, .r + + ; both zero? + add rcx, rax, rdx + jrcxz .r + + inc rsi + jmp .l + +.r: + sub rax, rdx + ret + +; +; int strncmp(const char *str1, const char *str2, int maxn) +; +strncmp: + mov rcx, ax2 + jrcxz .r + +.l: + movzx rax, b[ax0] + movzx rdx, b[ax1] + + bne rax, rdx, .r + + inc ax0 + inc ax1 + loop .l + +.r: + sub rax, rdx + ret + +; +; char *strchrnul(const char *str, int ch) +; +strchrnul: + mov rcx, 0x7AFFFFFF + scasb ax0, ax1 + + mov rax, ax0 + ret + +; +; char *strchr(const char *str, int ch) +; +strchr: + mov rcx, 0x7AFFFFFF + scasb ax0, ax1 + + bnz b[ax0], .r + nul rax + ret + +.r: + mov rax, ax0 + ret + +; +; void strrev(char *buf, const char *str) +; +; buf and src must NOT overlap +; +strrev: + bzr b[ax1], .z + + ; save str's location + mov rsi, ax1 + + ; go to str's end, just before + ; the null terminator + mov rcx, 0x7AFFFFFF + scasb ax1, zero + dec ax1 + +.l: + ; copy, going backward though str + ; and forward through buf + mov b[ax0], b[ax1] + + beq ax1, rsi, .r + + inc ax0 + dec ax1 + jmp .l + +.r: + nul b[ax0+1] + ret + +.z: + nul b[ax0] + ret + +; +; void strrev2(char *str) +; +; Inverses str +; +strrev2: + bzr b[ax0], .r + + mov ax1, ax0 + + ; go to str's end, just before + ; the null terminator + mov rcx, 0x7AFFFFFF + scasb ax1, zero + dec ax1 + + ; increase ax0 while decreasing ax1, performing exchanges +.l: + blteu ax1, ax0, .r + + xchg b[ax0], b[ax1] + + inc ax0 + dec ax1 + jmp .l + +.r: + ret +# 44 "crt/crt.k" 2 +# 1 "./crt/mem.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; void memcpy(void *, const void *, int) +; +memcpy: + mov rcx, ax2 + jrcxz .r + +.l: + sub rdx, ax2, rcx + mov b[ax0+rdx], b[ax1+rdx] + loop .l + +.r: + ret + +; +; void memzero(void *, int) +; +memzero: + mov rcx, ax1 + jrcxz .r + +.l: + nul b[ax0] + inc ax0 + loop .l + +.r: + ret +# 45 "crt/crt.k" 2 +# 1 "./crt/time.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; struct TIME +; { +; byte sec; +0 (0-59) +; byte min; +1 (0-59) +; byte hour; +2 (0-23) +; byte mday; +3 (0-31) +; byte month; +4 (0-11) +; byte; +5 (pad) +; word year; +6 (0-65536) +; word yday; +8 (0-365) +; word; +10 (pad) +; dword; +12 (pad) +; } 16 bytes +; + +; +; int DaysInYear(int year) +; +DaysInYear: + mov rax, 365 + + ; divisible by 4? + rem rcx, ax0, 4 + jrcxnz .end + + ; divisible by 100? + rem rcx, ax0, 100 + jrcxnz .leap + + ; divisible by 400? + rem rcx, ax0, 400 + jrcxnz .end + +.leap: + inc rax, 1 + +.end: + ret + +; +; TIME *GetTimeUTC(void) +; +GetTimeUTC: + ytime + + mov rdi, .buf + + ; seconds + rem rsi, rax, 60 + mov b[rdi], rsi + + ; minutes + div rsi, rax, 60 + rem rsi, 60 + mov b[rdi+1], rsi + + ; hours + div rsi, rax, 3600 + rem rsi, 24 + mov b[rdi+2], rsi + + ; month days + div rsi, rax, 3600*24 + mov b[rdi+3], rsi + + ; month + mov b[rdi+4], rbx + + ; years + mov w[rdi+6], rcx + + ; + ; ydays (TODO) + ; + + mov rax, .buf + ret + +.buf = [24] +# 46 "crt/crt.k" 2 + +# 1 "./crt/fmt/ltostr.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; void itoa(char *buf, int num, int base) +; +itoa: + mov ax3, 1 + jmp ltostr + +; +; void utoa(char *buf, int num, int base) +; +utoa: + nul ax3 + jmp ltostr + +; +; void ltostr(char *buf, int num, int base, bool signed) +; +ltostr: + mov rax, ax0 + nul rcx + + ; make sure base is in [2, 32] + bltu ax2, 2, .bad + bltu 36, ax2, .bad + + ; deal with zero + bzr ax1, .is_zero + + ; deal with base 10 signedness + + bzr ax3, .conv + bne ax2, 10, .conv ; base 10 + + shr rcx, ax1, 63 ; extract ax1 sign + jrcxz .conv + + neg ax1 ; NEG if negative + + ; main loop +.conv: + bzr ax1, .fini + + rem rdx, ax1, ax2 ; ax1 % base + + blt 9, rdx, .nondec ; rdx > 9 ? + + inc rdx, '0' + jmp .next + +.nondec: + inc rdx, 55 ; 'A' - 10 + +.next: + mov b[ax0], rdx + + inc ax0 + + div ax1, ax2 + jmp .conv + + ; add minus flag, null-terminate and reverse +.fini: + jrcxz .cxz + mov b[ax0], '-' + inc ax0 + +.cxz: + nul b[ax0] + + call strrev2, rax + ret + +; +; exceptional cases +; +.bad: + mov b[ax0], 0 + ret + +.is_zero: + mov b[ax0], 48 ; '0' + mov b[ax0+1], 0 + ret +# 48 "crt/crt.k" 2 +# 1 "./crt/fmt/strtol.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int strtol(const char *str, int base) +; +; rax = integer extracted from str +; rdx = pointer to first invalid byte +; +strtol: + mov ax2, 1 + jmp strtoq + +; +; int strtoul(const char *str, int base) +; +; rax = integer extracted from str +; rdx = pointer to first invalid byte +; +strtoul: + nul ax2 + jmp strtoq + +; +; int strtoq(const char *str, int base, bool signed) +; +; guesses base when 'base'=0 +; +strtoq: + nul rax, rsi + mov rdx, ax0 + + ; make sure base is in [2, 32] + beq ax1, 1, .bad + bltu 36, ax1, .bad + + ; empty string? + bzr b[rdx], .done + +.skip_spc: + bne b[rdx], ' ', .no_spc + inc rdx + jmp .skip_spc + +.no_spc: + ; skip + + bne b[rdx], '+', .no_plus + inc rdx + +.no_plus: + ; unsigned? + bzr ax2, .unsigned + + ; parse '-' + bne b[rdx], '-', .unsigned + inc rdx + mov rsi, 1 + +.unsigned: + ; base 0 + bzr ax1, .base_0 + + ; base prefix? + bne b[rdx], '0', .main_loop + + inc rdx + movzx rcx, b[rdx] + + ; "0x"/"0b" prefix + jrcxz .done ; "0" + beq rcx, 'x', .parsed_0x + beq rcx, 'b', .parsed_0b + + ; may be octal, but we don't care + ; we accept "0110101010" (despite base=2) for instance + jmp .main_loop + +.parsed_0x: + ; are we in base 16? + ; if not, leave rax = 0 and *rdx = 'x' + bne ax1, 16, .done + ; else + inc rdx, 1 + jmp .main_loop + +.parsed_0b: + ; are we in base 2? + ; if not, leave rax = 0 and *rdx = 'b' + bne ax1, 2, .done + ; else + inc rdx + jmp .main_loop + +.base_0: + ; guess base + + beq b[rdx], '0', .b0_not10 + + ; must be base 10 + mov ax1, 10 + jmp .main_loop + +.b0_not10: + inc rdx + + bne b[rdx], 'x', .b0_not16 + inc rdx + mov ax1, 16 + jmp .main_loop + +.b0_not16: + bne b[rdx], 'b', .b0_not2 + inc rdx + mov ax1, 2 + jmp .main_loop + +.b0_not2: + ; take octal by default + mov ax1, 8 + +.main_loop: + movzx rcx, b[rdx] + inc rdx + + ; between 0 and 9? + bltu rcx, '0', .done + bltu '9', rcx, .not_digit10 + + ; yes + sub rcx, '0' + jmp .next + +.not_digit10: + bltu rcx, 'A', .done + bltu 'Z', rcx, .not_digitAZ + + sub rcx, 55 ; 'A' - 10 + jmp .next + +.not_digitAZ: + bltu rcx, 'a', .done + bltu 'z', rcx, .done + + sub rcx, 87 ; 'a' - 10 + jmp .next + +.next: + ; too large for base? + blteu ax1, rcx, .done + + mul rax, ax1 + inc rax, rcx + jmp .main_loop + +.done: + ; negative? + bzr rsi, .r + + ; yes + neg rax + +.r: + ret + +.bad: + ret +# 49 "crt/crt.k" 2 +# 1 "./crt/fmt/doprnt.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; typedef int (*PUTC)(int ch) +; int doprnt(PUTC putc, int n, const char *fmt, va_list ap) +; +doprnt: + push rbp + mov rbp, rsp + + push r12, r13, r14 + push r15, r16, r17 + + mov r12, ax2 ; fmt + mov r14, ax3 ; va_list + mov r15, ax1 ; n + mov r17, ax0 ; putc + nul r16 ; return value + +.main_loop: + ; find '%' or null-terminator + mov rcx, 0x7AFFFFFF + mov r13, r12 + scasb r13, '%' + + ; everything below r13 is a regular character; print it +.print_regular: + beq r12, r13, .check_modf + + call .doput, b[r12] + + inc r12, 1 + jmp .print_regular + +.check_modf: + ; did we find a '%' ? + ; if not, then we found fmt's null-terminator; we're done + bne b[r12], '%', .epilogue + + ; we did find a modifier / '%' + mov rax, b[r12+1] + inc r12, 2 + + beq rax, 's', .modf_s + beq rax, 'c', .modf_c + beq rax, 'p', .modf_p + beq rax, 'x', .modf_x + beq rax, 'd', .modf_d + beq rax, 'o', .modf_o + beq rax, 'b', .modf_b + beq rax, '%', .modf_percent + + ; unrecognized + jmp .bad_modifier + +.modf_s: + ; get string address + mov r13, q[r14] + inc r14, 8 + + bzr r13, .nullstring + +.print_string: + movzx ax0, b[r13] + bzr ax0, .main_loop + + inc r13 + call .doput + + jmp .print_string + +.modf_c: + call .doput, q[r14] + inc r14, 8 + + jmp .main_loop + +.modf_p: + call .doput, '0' + call .doput, 'x' + ; Fallthrough + +.modf_x: + mov ax2, 16 + jmp .print_number + +.modf_d: + mov ax2, 10 + jmp .print_number + +.modf_o: + mov ax2, 8 + jmp .print_number + +.modf_b: + mov ax2, 2 + jmp .print_number + +.print_number: + ; allocate itoa conversion buffer + sub rsp, 80 + mov r13, rsp + + ; assume modifier already set up ax2 + call itoa, rsp, q[r14] + inc r14, 8 + +.print_itoa_buf: + movzx ax0, b[r13] + + bzr ax0, .pib_end_loop + inc r13 + + call .doput + jmp .print_itoa_buf + +.pib_end_loop: + inc rsp, 80 + jmp .main_loop + +.modf_percent: + call .doput, '%' + jmp .main_loop + +.bad_modifier: + ; print "%?" to clearly indicate that something is wrong + call .doput, '%' + call .doput, '?' + + jmp .main_loop + +.nullstring: + ; %s was passed a NULL + call .doput, '(' + call .doput, 'n' + call .doput, 'u' + call .doput, 'l' + call .doput, 'l' + call .doput, ')' + + jmp .main_loop + +.epilogue: + mov rax, r16 + + pop r17, r16 + pop r15, r14 + pop r13, r12 + + leave + ret +; +; prints ax0 +; +.doput: + ; update print count + inc r16 + + ; if n==0, don't print + ; we follow the C convention that sprintf()-like functions + ; should return the number of characters that would have + ; been printed/written if 'n' were big enough + bzr r15, .r + + ; decrement n and print + dec r15 + call r17 + + ; did putc fail? + jraxz .r + + ; yes, so artificially set n=0 + nul r15 + +.r: + ret +# 50 "crt/crt.k" 2 +# 1 "./crt/fmt/printf.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int putc(int ch) +; +putc: + prn ax0 + nul rax + ret + +; +; int printf(const char *fmt, ...) +; +printf: + mov ax2, ax0 + mov ax0, putc + mov ax1, 0x7AFFFFFF + add ax3, rsp, 8 + jmp doprnt + +; +; int nprintf(const char *fmt, int n, ...) +; +nprintf: + mov ax2, ax0 + mov ax0, putc + add ax3, rsp, 8 + jmp doprnt + +; +; Print a string +; Guaranteed to only affect rcx and ax0 +; +print: +.l: + movzx rax, b[ax0] + jraxz .r + + prn rax + inc ax0 + + jmp .l + +.r: + ret + +; +; Print exactly ax1 characters +; +nprint: + mov rcx, ax1 + jrcxz .r + +.l: + prn b[ax0] + inc ax0 + loop .l +.r: + ret +# 51 "crt/crt.k" 2 + +exit: + mov rax, Sys.Exit + trap 0 + +abort: + crash +# 8 "doskrnl.k" 2 + +; +; Special addresses +; + + + + + + +; The kernel guarantees that COMMAND.COM +; will always be loaded on a 16KB boundary +; This is guaranteed to be the case in all +; future versions as well + + + +; +; Entry point +; +start: + mov rsp, 0x104000 ; + 16KB + mov rbp, zero + + ;dump + + call main + + crash + +; +; Disk Operating System +; + +# 1 "sys/drv/cpudev.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +CPUDEV := 0 + +RFS.GetMaxIdx: + iocall CPUDEV, 16 + ret + +RFS.GetUsage: + iocall CPUDEV, 17 + ret + +RFS.GetCurIdx: + iocall CPUDEV, 18 + ret + +RFS.GetLeastAvail: + iocall CPUDEV, 19 + ret + +RFS.IsFrameActive: + iocall CPUDEV, 20 + ret + +RFS.ActivateFrame: + iocall CPUDEV, 21 + ret + +RFS.DeactivateFrame: + iocall CPUDEV, 22 + ret + +RFS.CopyFrame: + iocall CPUDEV, 23 + ret + +RFS.MoveFrame: + iocall CPUDEV, 24 + ret + +RFS.SwitchFrame: + iocall CPUDEV, 25 + ret + +RFS.LoadArgs: + iocall CPUDEV, 32 + ret + +RFS.LoadReg: + iocall CPUDEV, 33 + ret + +RFS.StoreReg: + iocall CPUDEV, 48 + ret + +IDT.AddHandler: + iocall CPUDEV, 64 + ret + +IDT.DelHandler: + iocall CPUDEV, 65 + ret + +IDT.QueryHandler: + iocall CPUDEV, 66 + +IDT.DoneHandling: + iocall CPUDEV, 67 + ret +# 43 "doskrnl.k" 2 +# 1 "sys/drv/memdev.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +MEMDEV := 1 + +MEM.GetMemOff: + iocall MEMDEV, 0 + ret + +MEM.GetMemSize: + iocall MEMDEV, 1 + ret +# 44 "doskrnl.k" 2 +# 1 "sys/drv/diskdev.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +DISKDEV := 4 + +DISK.FindFirst: +.slot := 16 + iocall DISKDEV, .slot + ret + +DISK.FindNext: +.slot := 17 + iocall DISKDEV, .slot + ret + +DISK.OpenFile: + iocall DISKDEV, 25 + ret + +DISK.CloseFile: + iocall DISKDEV, 26 + ret + +DISK.CreateFile: + iocall DISKDEV, 27 + ret + +DISK.RemoveFile: + iocall DISKDEV, 28 + ret + +DISK.ReadFile: + iocall DISKDEV, 32 + ret + +DISK.WriteFile: + iocall DISKDEV, 33 + ret +# 45 "doskrnl.k" 2 + +# 1 "sys/intr/excepts.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +DefaultExceptionHandler: + mov rsp, 0x105800 ; + 22KB + mov ax1, r12 + + bzr r12, DefaultTrapHandler.handle_Shutdown + bltu 11, r12, .unknown + + mov rsi, .err_ukn + lea ax0, b[rsi + r12 * 32] + call ScreenOfDeath + +.unknown: + call ScreenOfDeath, .err_ukn + crash + +.err_ukn = "Unknown exception number\0\0\0\0\0\0\0" +.err_udf = "Undefined behaviour exception\0\0" +.err_ill = "Illformed instruction exception" +.err_acc = "Invalid memory access exception" +.err_sys = "Supervisor-only instruct. used\0" +.err_dbf = "Double fault exception~~~~~~~~\0" +.err_imp = "Feat. not implemented exception" +.err_ali = "Misalignmed address exception\0\0" +.err_brk = "BREAK key (Ctrl+C) exception\0\0\0" +.err_ovf = "Overflow flag raised exception\0" +.err_div = "Division by zero exception\0\0\0\0\0" +# 47 "doskrnl.k" 2 +# 1 "sys/intr/common.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +ScreenOfDeath: + push r12 + mov r12, ax0 + + prn 10 + prn 0x8BF00001 + prn 0x8BF00002 + + push ax1 + call printf, .scr1 + pop + + bnz r14, .not_con + push .scr2_con + jmp .do_print + +.not_con: + ; rframes #2-#3-#4 belong to kernel + blt 4, r14, .not_krn + push .scr2_krn + jmp .do_print + +.not_krn: + push .scr2_usr + +.do_print: + push r14, r12 + call printf, .scr2 + inc rsp, 24 + + call dumprf, r14 + prn 10 + + call print, .scr3 + +.loop: + pause + pause + + scan rax + jraxz .loop + + beq rax, 0x0A, DefaultTrapHandler.handle_Exit + beq rax, 0x1B, DefaultTrapHandler.handle_Shutdown + + jmp .loop + + crash + +.scr1 = "-------- Unhandled EXCEPTION, TRAP/SYSCALL or INTERRUPT (code %d)\n" +.scr2 = "Description: %s\nFrame: %d (%s)\n\n" +.scr3 = "Press:\n ENTER to procede to COMMAND.COM\n ESCAPE to shutdown machine\n\n" + +.scr2_con = "dedicated command.com frame" +.scr2_usr = "userspace application frame" +.scr2_krn = "exception/interrupt handler" +# 48 "doskrnl.k" 2 +# 1 "sys/intr/trap0.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +TrapHandlers.prolog: + nul rbp + + ; rax = caller's cr1 + call RFS.LoadReg, r14, $cr1 + + ; we don't preserve the r12 we got + mov r12, rax + nul rdx + + jmp rcx + +TrapHandlers.epilog: + ; TRAP return values: RAX-RDX + + mov ax2, rax + call RFS.StoreReg, r14, $rax + + mov ax2, rdx + call RFS.StoreReg, r14, $rdx + + call IDT.DoneHandling, r13 + + iret + +; +; TRAP #0 handler +; +DefaultTrapHandler: + +.init: + mov rcx, .text + mov rsp, 0x104800 ; + 18KB + jmp TrapHandlers.prolog + +.text: + call RFS.LoadReg, r14, $rax + call RFS.LoadArgs, r14 + + ; will be optimized with a table + ; when we have a "finished" consistent API + jraxz .handle_Shutdown + beq rax, Sys.Exit, .handle_Exit + beq rax, Sys.ExecuteInFrame, .handle_EIF + beq rax, Sys.ReadFile, .handle_ReadFile + beq rax, Sys.OpenFile, .handle_OpenFile + beq rax, Sys.CloseFile, .handle_CloseFile + beq rax, Sys.CreateFile, .handle_CreateFile + beq rax, Sys.RemoveFile, .handle_RemoveFile + beq rax, Sys.FindNext, .handle_FindNext + beq rax, Sys.FindFirst, .handle_FindFirst + beq rax, Sys.EnterHaltMode, .handle_HaltMode + +.sod: + call ScreenOfDeath, .badsyscall, r13 + +.wrong: + call ScreenOfDeath, .badparams, r13 + +.fini.savecx: + mov ax2, rcx + call RFS.StoreReg, r14, $rcx + +.fini: + jmp TrapHandlers.epilog + +.badsyscall = "Invalid syscall number in RAX" +.badparams = "Invalid syscall parameters" + +;------------------------------------------------; +; Syscall implementations ; +;------------------------------------------------; + +; +; Pass control to COMMAND.COM in frame 0 +; +.handle_Exit: + ; deactivate current rframe + bzr r14, .hE_nz ; unless it's 0... + call RFS.DeactivateFrame, r14 + +.hE_nz: + ; open COMMAND.COM + call DISK.OpenFile, .cmdcom + + ; crash on failure + bltz rax, abort + + ; load at 0x108000 ; + 32KB + mov ax1, 0x108000 ; + 32KB + mov ax2, 0x80000 ; 512KB + call DISK.ReadFile, rax + + ; assume that COMMAND.COM being + ; less then 4KB means something + ; went wrong + blt rax, 0x1000, abort + + ; close the handle + call DISK.CloseFile, rax + + ; code address + mov ax2, 0x100000 + call RFS.StoreReg, zero, $rip + + ; usermode + mov ax2, 3 + call RFS.StoreReg, zero, $cr0 + + mov rcx, 0x108000 ; + 32KB + sub rcx, 0x100000 + + ; code offset + mov ax2, rcx + call RFS.StoreReg, zero, $cr1 + + ; return frame + nul r14 + + jmp .fini + +.cmdcom = "command.com" + +.handle_EIF: + blt ax1, 5, .wrong + + ; RIP can't be <1MB + mov rcx, 0x100000 + blt ax0, rcx, .wrong + + ; add old CR1 + add ax0, r12 + + ; real RIP can't be <1MB+32KB either + ; (kernel + cmdcom personal space) + add rdx, rcx, 0x8000 + blt ax0, rcx, .wrong + + ; save rframe and compute new CR1 + mov rbx, ax1 + sub rdi, ax0, rcx + + ; activate rframe + call RFS.ActivateFrame, rbx + + ; save new CR1 + mov ax2, rdi + call RFS.StoreReg, rbx, $cr1 + + ; interruptible user mode + mov ax2, 3 + call RFS.StoreReg, rbx, $cr0 + + ; set RIP = 1MB + mov ax2, rcx + call RFS.StoreReg, rbx, $rip + + ; change return frame + mov r14, rbx + + jmp .fini + +; +; Disk API +; +.handle_FindFirst: + inc ax0, r12 + call DISK.FindFirst + jmp .fini.savecx + +.handle_FindNext: + inc ax0, r12 + call DISK.FindNext + jmp .fini.savecx + +.handle_OpenFile: + inc ax0, r12 + call DISK.OpenFile + jmp .fini + +.handle_CreateFile: + inc ax0, r12 + call DISK.CreateFile + jmp .fini + +.handle_RemoveFile: + inc ax0, r12 + call DISK.RemoveFile + jmp .fini + +.handle_CloseFile: + call DISK.CloseFile + jmp .fini + +.handle_ReadFile: + inc ax1, r12 + call DISK.ReadFile + jmp .fini + +; +; Misc. +; +.handle_Shutdown: + call IDT.DelHandler, zero + stop + +.handle_HaltMode: + pause + pause + hlt + jmp .handle_HaltMode +# 49 "doskrnl.k" 2 + +# 1 "sys/dumprf.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +dumprf: + push rbp + mov rbp, rsp + + push r12, r13 + mov r12, ax0 + mov r13, rsp + + call RFS.LoadReg, r12, $cr1 + push rax + + call RFS.LoadReg, r12, $cr0 + push rax + + call RFS.LoadReg, r12, $rip + push rax + + push r12 + call printf, .dmp1 + + call RFS.LoadReg, r12, $srp + push rax + + call RFS.LoadReg, r12, $trp + push rax + + push zero # fixme + + call RFS.LoadReg, r12, $rbp + push rax + + call RFS.LoadReg, r12, $rsp + push rax + + call printf, .dmp2 + + call RFS.LoadReg, r12, $ax5 + push rax + + call RFS.LoadReg, r12, $ax4 + push rax + + call RFS.LoadReg, r12, $ax3 + push rax + + call RFS.LoadReg, r12, $ax2 + push rax + + call RFS.LoadReg, r12, $ax1 + push rax + + call RFS.LoadReg, r12, $ax0 + push rax + + call printf, .dmp3 + + call RFS.LoadReg, r12, $rdi + push rax + + call RFS.LoadReg, r12, $rsi + push rax + + call RFS.LoadReg, r12, $rdx + push rax + + call RFS.LoadReg, r12, $rcx + push rax + + call RFS.LoadReg, r12, $rbx + push rax + + call RFS.LoadReg, r12, $rax + push rax + + call printf, .dmp4 + + call RFS.LoadReg, r12, $r17 + push rax + + call RFS.LoadReg, r12, $r16 + push rax + + call RFS.LoadReg, r12, $r15 + push rax + + call RFS.LoadReg, r12, $r14 + push rax + + call RFS.LoadReg, r12, $r13 + push rax + + call RFS.LoadReg, r12, $r12 + push rax + + call printf, .dmp5 + + call RFS.LoadReg, r12, $r20 + push rax + + call RFS.LoadReg, r12, $r19 + push rax + + call RFS.LoadReg, r12, $r18 + push rax + + call printf, .dmp6 + + mov rsp, r13 + pop r13, r12 + + leave + ret + +.dmp1 = "Environ #1:\nfrm=0d%d rip=0x%x\ncr0=0x%x cr1=0x%x\n\n" +.dmp2 = "Environ #2:\nrsp=0x%x rbp=0x%x ind=0d%d\ngrp=0x%x trp=0x%x srp=0x%x\n\n" +.dmp3 = "Argument:\nax0=0x%x ax1=0x%x ax2=0x%x\nax3=0x%x ax4=0x%x ax5=0x%x\n\n" +.dmp4 = "Volatile:\nrax=0x%x rbx=0x%x rcx=0x%x\nrdx=0x%x rsi=0x%x rdi=0x%x\n\n" +.dmp5 = "Persistent:\nr12=0x%x r13=0x%x r14=0x%x\nr15=0x%x r16=0x%x r17=0x%x\n" +.dmp6 = "r18=0x%x r19=0x%x r20=0x%x\n" +# 51 "doskrnl.k" 2 +# 1 "sys/main.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +PrintBootMsg: + call print, .bootmsg + ret + +.bootmsg = "Starting DOS...\n\n" + +; +; Initialize TRAP handlers +; +InitSyscalls: + call RFS.ActivateFrame, 1 + + mov ax1, $rip + mov ax2, DefaultTrapHandler + call RFS.StoreReg + + mov ax0, 256 + mov ax1, 1 + call IDT.AddHandler + + ret + +InitExcepts: + call RFS.ActivateFrame, 2 + + mov ax1, $rip + mov ax2, DefaultExceptionHandler + call RFS.StoreReg + + call IDT.AddHandler, zero, 2 + +SwitchToCMD: + mov rax, Sys.Exit + trap 0 + +; +; Main function +; +main: + call PrintBootMsg + call InitSyscalls + call InitExcepts + call SwitchToCMD + ret +# 52 "doskrnl.k" 2 diff --git a/fs/hello.ppc b/fs/hello.ppc new file mode 100644 index 0000000..eb98642 --- /dev/null +++ b/fs/hello.ppc @@ -0,0 +1,995 @@ +# 1 "hello.k" +# 1 "" +# 1 "" +# 31 "" +# 1 "/usr/include/stdc-predef.h" 1 3 4 +# 32 "" 2 +# 1 "hello.k" +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +start: + jmp main + +# 1 "crt/crt.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; Limits +; +# 31 "crt/crt.k" +; +; Magic numbers +; + + + + +; +; CRT librairies +; + +# 1 "./crt/sys.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; SHUTDOWN syscall +; +; End virtual machine +; +Sys.Shutdown := 0x00 + +; +; EXIT syscall +; +; Return to COMMAND.COM +; +Sys.Exit := 0x01 + +; +; EXEC syscall +; +; IN +; ax0 = new RIP to load +; ax1 = frame ID to switch to +; +; New frame ID must be higher than current +; frame ID, and cannot be below 5 +; +; New CR1 is set so that new RIP corresponds to 1MB +; +Sys.ExecuteInFrame := 0x02 + +; FIND syscalls +; +; Find file on disk +; +; IN +; ax0 = address of name buffer +; ax1 = size of name buffer +; +; OUT +; rax = # of bytes written in name buffer +; rdx = size of file +; +; +Sys.FindFirst := 0x20 +Sys.FindNext := 0x21 + +; +; OPEN syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = handle of file, or <0 if couldn't open +; +Sys.OpenFile := 0x30 + +; +; CREATE syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = 0 on success, or <0 if couldn't open +; +Sys.CreateFile := 0x31 + +; +; REMOVE syscall +; +; IN +; ax0 = name string +; +; OUT +; rax = 0 on success, or <0 if couldn't open +; +Sys.RemoveFile := 0x32 + +; +; CLOSE syscall +; +; IN +; ax0 = file handle +; +Sys.CloseFile := 0x35 + +; +; READ syscall +; +; IN +; ax0 = file handle +; ax1 = buffer address +; ax2 = buffer size +; +; OUT +; rax = number of bytes read, <0 on error +; +Sys.ReadFile := 0x38 + +; Halt mode +Sys.EnterHaltMode := 0x999 +# 43 "crt/crt.k" 2 +# 1 "./crt/str.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int strnlen(char *, int) +; +strnlen: + mov rcx, ax1 + scasb ax0, zero + + sub rax, ax1, rcx + ret + +; +; int strlen(char *) +; +strlen: + mov rcx, 0x7AFFFFFF + mov rdx, rcx + scasb ax0, zero + + sub rax, rdx, rcx + ret + +; +; void strcpy(char *, const char *) +; +strcpy: +.l: + mov rcx, b[ax1] + mov b[ax0], rcx + + jrcxz .r + + inc ax0 + inc ax1 + + jmp .l + +.r: + ret + +; +; void strncpy(char *, const char *, int) +; +strncpy: + mov rcx, ax2 + jrcxz .r + +.l: + mov b[ax0], b[ax1] + + inc ax0 + inc ax1 + + loop .l + +.r: + ret + +; +; void strnzcpy(char *, const char *, int) +; +strnzcpy: + mov rcx, ax2 + jrcxz .r + +.l: + mov rax, b[ax1] + mov b[ax0], rax + + jraxz .r + + inc ax0 + inc ax1 + + loop .l + +.z: + nul b[ax0] + +.r: + ret + +; +; int strcmp(const char *str1, const char *str2) +; +; Returns: +; 0 if the contents of both strings are equal +; >0 if the first character that does not match has a greater value in str1 than in str2 +; <0 if the first character that does not match has a lower value in str1 than in str2 +; +strcmp: + nul rsi +.l: + movzx rax, b[ax0+rsi] + movzx rdx, b[ax1+rsi] + + bne rax, rdx, .r + + ; both zero? + add rcx, rax, rdx + jrcxz .r + + inc rsi + jmp .l + +.r: + sub rax, rdx + ret + +; +; int strncmp(const char *str1, const char *str2, int maxn) +; +strncmp: + mov rcx, ax2 + jrcxz .r + +.l: + movzx rax, b[ax0] + movzx rdx, b[ax1] + + bne rax, rdx, .r + + inc ax0 + inc ax1 + loop .l + +.r: + sub rax, rdx + ret + +; +; char *strchrnul(const char *str, int ch) +; +strchrnul: + mov rcx, 0x7AFFFFFF + scasb ax0, ax1 + + mov rax, ax0 + ret + +; +; char *strchr(const char *str, int ch) +; +strchr: + mov rcx, 0x7AFFFFFF + scasb ax0, ax1 + + bnz b[ax0], .r + nul rax + ret + +.r: + mov rax, ax0 + ret + +; +; void strrev(char *buf, const char *str) +; +; buf and src must NOT overlap +; +strrev: + bzr b[ax1], .z + + ; save str's location + mov rsi, ax1 + + ; go to str's end, just before + ; the null terminator + mov rcx, 0x7AFFFFFF + scasb ax1, zero + dec ax1 + +.l: + ; copy, going backward though str + ; and forward through buf + mov b[ax0], b[ax1] + + beq ax1, rsi, .r + + inc ax0 + dec ax1 + jmp .l + +.r: + nul b[ax0+1] + ret + +.z: + nul b[ax0] + ret + +; +; void strrev2(char *str) +; +; Inverses str +; +strrev2: + bzr b[ax0], .r + + mov ax1, ax0 + + ; go to str's end, just before + ; the null terminator + mov rcx, 0x7AFFFFFF + scasb ax1, zero + dec ax1 + + ; increase ax0 while decreasing ax1, performing exchanges +.l: + blteu ax1, ax0, .r + + xchg b[ax0], b[ax1] + + inc ax0 + dec ax1 + jmp .l + +.r: + ret +# 44 "crt/crt.k" 2 +# 1 "./crt/mem.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; void memcpy(void *, const void *, int) +; +memcpy: + mov rcx, ax2 + jrcxz .r + +.l: + sub rdx, ax2, rcx + mov b[ax0+rdx], b[ax1+rdx] + loop .l + +.r: + ret + +; +; void memzero(void *, int) +; +memzero: + mov rcx, ax1 + jrcxz .r + +.l: + nul b[ax0] + inc ax0 + loop .l + +.r: + ret +# 45 "crt/crt.k" 2 +# 1 "./crt/time.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; struct TIME +; { +; byte sec; +0 (0-59) +; byte min; +1 (0-59) +; byte hour; +2 (0-23) +; byte mday; +3 (0-31) +; byte month; +4 (0-11) +; byte; +5 (pad) +; word year; +6 (0-65536) +; word yday; +8 (0-365) +; word; +10 (pad) +; dword; +12 (pad) +; } 16 bytes +; + +; +; int DaysInYear(int year) +; +DaysInYear: + mov rax, 365 + + ; divisible by 4? + rem rcx, ax0, 4 + jrcxnz .end + + ; divisible by 100? + rem rcx, ax0, 100 + jrcxnz .leap + + ; divisible by 400? + rem rcx, ax0, 400 + jrcxnz .end + +.leap: + inc rax, 1 + +.end: + ret + +; +; TIME *GetTimeUTC(void) +; +GetTimeUTC: + ytime + + mov rdi, .buf + + ; seconds + rem rsi, rax, 60 + mov b[rdi], rsi + + ; minutes + div rsi, rax, 60 + rem rsi, 60 + mov b[rdi+1], rsi + + ; hours + div rsi, rax, 3600 + rem rsi, 24 + mov b[rdi+2], rsi + + ; month days + div rsi, rax, 3600*24 + mov b[rdi+3], rsi + + ; month + mov b[rdi+4], rbx + + ; years + mov w[rdi+6], rcx + + ; + ; ydays (TODO) + ; + + mov rax, .buf + ret + +.buf = [24] +# 46 "crt/crt.k" 2 + +# 1 "./crt/fmt/ltostr.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; void itoa(char *buf, int num, int base) +; +itoa: + mov ax3, 1 + jmp ltostr + +; +; void utoa(char *buf, int num, int base) +; +utoa: + nul ax3 + jmp ltostr + +; +; void ltostr(char *buf, int num, int base, bool signed) +; +ltostr: + mov rax, ax0 + nul rcx + + ; make sure base is in [2, 32] + bltu ax2, 2, .bad + bltu 36, ax2, .bad + + ; deal with zero + bzr ax1, .is_zero + + ; deal with base 10 signedness + + bzr ax3, .conv + bne ax2, 10, .conv ; base 10 + + shr rcx, ax1, 63 ; extract ax1 sign + jrcxz .conv + + neg ax1 ; NEG if negative + + ; main loop +.conv: + bzr ax1, .fini + + rem rdx, ax1, ax2 ; ax1 % base + + blt 9, rdx, .nondec ; rdx > 9 ? + + inc rdx, '0' + jmp .next + +.nondec: + inc rdx, 55 ; 'A' - 10 + +.next: + mov b[ax0], rdx + + inc ax0 + + div ax1, ax2 + jmp .conv + + ; add minus flag, null-terminate and reverse +.fini: + jrcxz .cxz + mov b[ax0], '-' + inc ax0 + +.cxz: + nul b[ax0] + + call strrev2, rax + ret + +; +; exceptional cases +; +.bad: + mov b[ax0], 0 + ret + +.is_zero: + mov b[ax0], 48 ; '0' + mov b[ax0+1], 0 + ret +# 48 "crt/crt.k" 2 +# 1 "./crt/fmt/strtol.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int strtol(const char *str, int base) +; +; rax = integer extracted from str +; rdx = pointer to first invalid byte +; +strtol: + mov ax2, 1 + jmp strtoq + +; +; int strtoul(const char *str, int base) +; +; rax = integer extracted from str +; rdx = pointer to first invalid byte +; +strtoul: + nul ax2 + jmp strtoq + +; +; int strtoq(const char *str, int base, bool signed) +; +; guesses base when 'base'=0 +; +strtoq: + nul rax, rsi + mov rdx, ax0 + + ; make sure base is in [2, 32] + beq ax1, 1, .bad + bltu 36, ax1, .bad + + ; empty string? + bzr b[rdx], .done + +.skip_spc: + bne b[rdx], ' ', .no_spc + inc rdx + jmp .skip_spc + +.no_spc: + ; skip + + bne b[rdx], '+', .no_plus + inc rdx + +.no_plus: + ; unsigned? + bzr ax2, .unsigned + + ; parse '-' + bne b[rdx], '-', .unsigned + inc rdx + mov rsi, 1 + +.unsigned: + ; base 0 + bzr ax1, .base_0 + + ; base prefix? + bne b[rdx], '0', .main_loop + + inc rdx + movzx rcx, b[rdx] + + ; "0x"/"0b" prefix + jrcxz .done ; "0" + beq rcx, 'x', .parsed_0x + beq rcx, 'b', .parsed_0b + + ; may be octal, but we don't care + ; we accept "0110101010" (despite base=2) for instance + jmp .main_loop + +.parsed_0x: + ; are we in base 16? + ; if not, leave rax = 0 and *rdx = 'x' + bne ax1, 16, .done + ; else + inc rdx, 1 + jmp .main_loop + +.parsed_0b: + ; are we in base 2? + ; if not, leave rax = 0 and *rdx = 'b' + bne ax1, 2, .done + ; else + inc rdx + jmp .main_loop + +.base_0: + ; guess base + + beq b[rdx], '0', .b0_not10 + + ; must be base 10 + mov ax1, 10 + jmp .main_loop + +.b0_not10: + inc rdx + + bne b[rdx], 'x', .b0_not16 + inc rdx + mov ax1, 16 + jmp .main_loop + +.b0_not16: + bne b[rdx], 'b', .b0_not2 + inc rdx + mov ax1, 2 + jmp .main_loop + +.b0_not2: + ; take octal by default + mov ax1, 8 + +.main_loop: + movzx rcx, b[rdx] + inc rdx + + ; between 0 and 9? + bltu rcx, '0', .done + bltu '9', rcx, .not_digit10 + + ; yes + sub rcx, '0' + jmp .next + +.not_digit10: + bltu rcx, 'A', .done + bltu 'Z', rcx, .not_digitAZ + + sub rcx, 55 ; 'A' - 10 + jmp .next + +.not_digitAZ: + bltu rcx, 'a', .done + bltu 'z', rcx, .done + + sub rcx, 87 ; 'a' - 10 + jmp .next + +.next: + ; too large for base? + blteu ax1, rcx, .done + + mul rax, ax1 + inc rax, rcx + jmp .main_loop + +.done: + ; negative? + bzr rsi, .r + + ; yes + neg rax + +.r: + ret + +.bad: + ret +# 49 "crt/crt.k" 2 +# 1 "./crt/fmt/doprnt.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; typedef int (*PUTC)(int ch) +; int doprnt(PUTC putc, int n, const char *fmt, va_list ap) +; +doprnt: + push rbp + mov rbp, rsp + + push r12, r13, r14 + push r15, r16, r17 + + mov r12, ax2 ; fmt + mov r14, ax3 ; va_list + mov r15, ax1 ; n + mov r17, ax0 ; putc + nul r16 ; return value + +.main_loop: + ; find '%' or null-terminator + mov rcx, 0x7AFFFFFF + mov r13, r12 + scasb r13, '%' + + ; everything below r13 is a regular character; print it +.print_regular: + beq r12, r13, .check_modf + + call .doput, b[r12] + + inc r12, 1 + jmp .print_regular + +.check_modf: + ; did we find a '%' ? + ; if not, then we found fmt's null-terminator; we're done + bne b[r12], '%', .epilogue + + ; we did find a modifier / '%' + mov rax, b[r12+1] + inc r12, 2 + + beq rax, 's', .modf_s + beq rax, 'c', .modf_c + beq rax, 'p', .modf_p + beq rax, 'x', .modf_x + beq rax, 'd', .modf_d + beq rax, 'o', .modf_o + beq rax, 'b', .modf_b + beq rax, '%', .modf_percent + + ; unrecognized + jmp .bad_modifier + +.modf_s: + ; get string address + mov r13, q[r14] + inc r14, 8 + + bzr r13, .nullstring + +.print_string: + movzx ax0, b[r13] + bzr ax0, .main_loop + + inc r13 + call .doput + + jmp .print_string + +.modf_c: + call .doput, q[r14] + inc r14, 8 + + jmp .main_loop + +.modf_p: + call .doput, '0' + call .doput, 'x' + ; Fallthrough + +.modf_x: + mov ax2, 16 + jmp .print_number + +.modf_d: + mov ax2, 10 + jmp .print_number + +.modf_o: + mov ax2, 8 + jmp .print_number + +.modf_b: + mov ax2, 2 + jmp .print_number + +.print_number: + ; allocate itoa conversion buffer + sub rsp, 80 + mov r13, rsp + + ; assume modifier already set up ax2 + call itoa, rsp, q[r14] + inc r14, 8 + +.print_itoa_buf: + movzx ax0, b[r13] + + bzr ax0, .pib_end_loop + inc r13 + + call .doput + jmp .print_itoa_buf + +.pib_end_loop: + inc rsp, 80 + jmp .main_loop + +.modf_percent: + call .doput, '%' + jmp .main_loop + +.bad_modifier: + ; print "%?" to clearly indicate that something is wrong + call .doput, '%' + call .doput, '?' + + jmp .main_loop + +.nullstring: + ; %s was passed a NULL + call .doput, '(' + call .doput, 'n' + call .doput, 'u' + call .doput, 'l' + call .doput, 'l' + call .doput, ')' + + jmp .main_loop + +.epilogue: + mov rax, r16 + + pop r17, r16 + pop r15, r14 + pop r13, r12 + + leave + ret +; +; prints ax0 +; +.doput: + ; update print count + inc r16 + + ; if n==0, don't print + ; we follow the C convention that sprintf()-like functions + ; should return the number of characters that would have + ; been printed/written if 'n' were big enough + bzr r15, .r + + ; decrement n and print + dec r15 + call r17 + + ; did putc fail? + jraxz .r + + ; yes, so artificially set n=0 + nul r15 + +.r: + ret +# 50 "crt/crt.k" 2 +# 1 "./crt/fmt/printf.k" 1 +; The OS/K Team licenses this file to you under the MIT license. +; See the LICENSE file in the project root for more information. + +; +; int putc(int ch) +; +putc: + prn ax0 + nul rax + ret + +; +; int printf(const char *fmt, ...) +; +printf: + mov ax2, ax0 + mov ax0, putc + mov ax1, 0x7AFFFFFF + add ax3, rsp, 8 + jmp doprnt + +; +; int nprintf(const char *fmt, int n, ...) +; +nprintf: + mov ax2, ax0 + mov ax0, putc + add ax3, rsp, 8 + jmp doprnt + +; +; Print a string +; Guaranteed to only affect rcx and ax0 +; +print: +.l: + movzx rax, b[ax0] + jraxz .r + + prn rax + inc ax0 + + jmp .l + +.r: + ret + +; +; Print exactly ax1 characters +; +nprint: + mov rcx, ax1 + jrcxz .r + +.l: + prn b[ax0] + inc ax0 + loop .l +.r: + ret +# 51 "crt/crt.k" 2 + +exit: + mov rax, Sys.Exit + trap 0 + +abort: + crash +# 8 "hello.k" 2 + +main: + mov rsp, 0x104000 + nul rbp + + call print, .hellow + + jmp exit + +.hellow = "Hello World!\n" diff --git a/ka/Makefile b/ka/Makefile index 4305ee5..0d80ac8 100644 --- a/ka/Makefile +++ b/ka/Makefile @@ -5,12 +5,15 @@ all: dos KODIR=../fs KSDIR=../vm/ob +KPDIR=../vm/ob KSRC = $(shell ls *.k) +KPPC = $(patsubst %.k,$(KODIR)/%.ppc,$(KSRC)) KCOM = $(patsubst %.k,$(KODIR)/%.com,$(KSRC)) $(KODIR)/%.com: %.k - @../as/k-as.py $< 0x100000 $@ $(patsubst %.k,$(KSDIR)/%.sym,$<) + @cpp -I. -x assembler-with-cpp $< > $(patsubst %.k,$(KODIR)/%.ppc,$<) + @../as/k-as.py $(patsubst %.k,$(KODIR)/%.ppc,$<) 0x100000 $@ $(patsubst %.k,$(KSDIR)/%.sym,$<) dos: $(KCOM) diff --git a/ka/command.k b/ka/command.k index 9809323..304a9be 100644 --- a/ka/command.k +++ b/ka/command.k @@ -4,16 +4,16 @@ cmdstart: jmp start -include "crt/crt.k" +#include "crt/crt.k" -CMDCOM_CODE := 0x100000 ; 1MB -CMDCOM_STACK := 0x104000 ; + 16KB +#define CMDCOM_CODE 0x100000 ; 1MB +#define CMDCOM_STACK 0x104000 ; + 16KB ; COMMAND.COM guarantees that programs ; are always loaded on a 16KB boundary ; This is guaranteed to be the case ; in all future versions as well -FILE_LOADP := 0x108000 ; + 32KB +#define FILE_LOADP 0x108000 ; + 32KB start: mov rsp, CMDCOM_STACK @@ -53,7 +53,7 @@ main: call memzero, argv0, argbuf.size nul q[argv1pos] - # call nprint, argv0, argbuf.size + ; call nprint, argv0, argbuf.size ; iterator through argbuf nul rcx @@ -224,9 +224,9 @@ main: ; call builtins ; -include "usr/cmd-dir.k" -include "usr/cmd-exec.k" -include "usr/cmd-fsmisc.k" +#include "usr/cmd-dir.k" +#include "usr/cmd-exec.k" +#include "usr/cmd-fsmisc.k" .handle_CLS: prn PRN_CLEAR diff --git a/ka/crt/crt.k b/ka/crt/crt.k index 6c7f16c..e743fed 100644 --- a/ka/crt/crt.k +++ b/ka/crt/crt.k @@ -5,49 +5,49 @@ ; Limits ; -CHAR_MIN := 0x80 -SHRT_MIN := 0x8000 -INT_MIN := 0x80000000 -LONG_MIN := 0x8000000000000000 +#define CHAR_MIN 0x80 +#define SHRT_MIN 0x8000 +#define INT_MIN 0x80000000 +#define LONG_MIN 0x8000000000000000 -XCHAR_MIN := 0xFFFFFFFFFFFFFF80 -XSHRT_MIN := 0xFFFFFFFFFFFF8000 -XINT_MIN := 0xFFFFFFFF80000000 +#define XCHAR_MIN 0xFFFFFFFFFFFFFF80 +#define XSHRT_MIN 0xFFFFFFFFFFFF8000 +#define XINT_MIN 0xFFFFFFFF80000000 -CHAR_MAX := 0x7F -SHRT_MAX := 0x7FFF -INT_MAX := 0x7FFFFFFF -LONG_MAX := 0x7FFFFFFFFFFFFFFF +#define CHAR_MAX 0x7F +#define SHRT_MAX 0x7FFF +#define INT_MAX 0x7FFFFFFF +#define LONG_MAX 0x7FFFFFFFFFFFFFFF -BYTE_MAX := 0xFF -WORD_MAX := 0xFFFF -LWORD_MAX := 0xFFFFFFFF -QWORD_MAX := 0xFFFFFFFFFFFFFFFF +#define BYTE_MAX 0xFF +#define WORD_MAX 0xFFFF +#define LWORD_MAX 0xFFFFFFFF +#define QWORD_MAX 0xFFFFFFFFFFFFFFFF -FNAME_MAX := 0x80 -FILE_MAXSZ := 0x8000 -STRLEN_MAX := 0x7AFFFFFF +#define FNAME_MAX 0x80 +#define FILE_MAXSZ 0x8000 +#define STRLEN_MAX 0x7AFFFFFF ; ; Magic numbers ; -PRN_CLEAR := 0x8BF00001 -PRN_FLUSH := 0x8BF00002 +#define PRN_CLEAR 0x8BF00001 +#define PRN_FLUSH 0x8BF00002 ; ; CRT librairies ; -include "crt/sys.k" -include "crt/str.k" -include "crt/mem.k" -include "crt/time.k" +#include "crt/sys.k" +#include "crt/str.k" +#include "crt/mem.k" +#include "crt/time.k" -include "crt/fmt/ltostr.k" -include "crt/fmt/strtol.k" -include "crt/fmt/doprnt.k" -include "crt/fmt/printf.k" +#include "crt/fmt/ltostr.k" +#include "crt/fmt/strtol.k" +#include "crt/fmt/doprnt.k" +#include "crt/fmt/printf.k" exit: mov rax, Sys.Exit diff --git a/ka/doskrnl.k b/ka/doskrnl.k index 025f318..4d95dd9 100644 --- a/ka/doskrnl.k +++ b/ka/doskrnl.k @@ -4,24 +4,23 @@ __sysmain: jmp start - -include "crt/crt.k" +#include "crt/crt.k" ; ; Special addresses ; -DOSKRNL_CODE := 0x100000 ; 1MB -DOSKRNL_STACK := 0x104000 ; + 16KB -TRAP0_STACK := 0x104800 ; + 18KB -INTR0_STACK := 0x105000 ; + 20KB -EXCT0_STACK := 0x105800 ; + 22KB +#define DOSKRNL_CODE 0x100000 ; 1MB +#define DOSKRNL_STACK 0x104000 ; + 16KB +#define TRAP0_STACK 0x104800 ; + 18KB +#define INTR0_STACK 0x105000 ; + 20KB +#define EXCT0_STACK 0x105800 ; + 22KB ; The kernel guarantees that COMMAND.COM ; will always be loaded on a 16KB boundary ; This is guaranteed to be the case in all ; future versions as well -CMDCOM_LOADP := 0x108000 ; + 32KB -CMDCOM_MAXSZ := 0x80000 ; 512KB +#define CMDCOM_LOADP 0x108000 ; + 32KB +#define CMDCOM_MAXSZ 0x80000 ; 512KB ; ; Entry point @@ -40,14 +39,14 @@ start: ; Disk Operating System ; -include "sys/drv/cpudev.k" -include "sys/drv/memdev.k" -include "sys/drv/diskdev.k" +#include "sys/drv/cpudev.k" +#include "sys/drv/memdev.k" +#include "sys/drv/diskdev.k" -include "sys/intr/excepts.k" -include "sys/intr/common.k" -include "sys/intr/trap0.k" +#include "sys/intr/excepts.k" +#include "sys/intr/common.k" +#include "sys/intr/trap0.k" -include "sys/dumprf.k" -include "sys/main.k" +#include "sys/dumprf.k" +#include "sys/main.k" diff --git a/ka/hello.k b/ka/hello.k index c34ca45..1b804dd 100644 --- a/ka/hello.k +++ b/ka/hello.k @@ -4,7 +4,7 @@ start: jmp main -include "crt/crt.k" +#include "crt/crt.k" main: mov rsp, 0x104000 diff --git a/ka/usr/cmd-dir.k b/ka/usr/cmd-dir.k index bac4e17..5e50049 100644 --- a/ka/usr/cmd-dir.k +++ b/ka/usr/cmd-dir.k @@ -158,5 +158,5 @@ N := 11 .dirmsg = "Directory of C:\\\n\n" .bytesstr = "%d kilobytes + %d bytes" -# .bytesstr = "%dMB + %dKB + %dB" # too soon +; .bytesstr = "%dMB + %dKB + %dB" # too soon diff --git a/ka/usr/cmd-exec.k b/ka/usr/cmd-exec.k index 696c4ab..3ef56a5 100644 --- a/ka/usr/cmd-exec.k +++ b/ka/usr/cmd-exec.k @@ -54,3 +54,4 @@ ; unreachable jmp abort + diff --git a/vm/la/kvisc.lang b/vm/la/kvisc.lang index fffdeb5..4efae10 100644 --- a/vm/la/kvisc.lang +++ b/vm/la/kvisc.lang @@ -10,7 +10,7 @@ \ - #|@|;|/ + @|;|/ @@ -26,6 +26,10 @@ [ ]*.[A-Za-z_0-9.]*\: + + ^\#(include|define|if|ifdef|else|endif|undef|pragma|error) + + " " @@ -37,10 +41,9 @@ - include + (include|define|if|ifdef|else|endif|undef|pragma|error)