diff --git a/README.md b/README.md index df4d4ea..1f6a303 100644 --- a/README.md +++ b/README.md @@ -469,7 +469,7 @@ This also means that when invoking the `exec` syscall in an assembly program, on ### Syscall documentation -The syscalls are all documented in the header files provided for both assembly and C, you will find [assembly headers here](https://github.com/Zeal8bit/Zeal-8-bit-OS/tree/main/kernel_headers/z88dk-z80asm) and [C headers here](https://github.com/Zeal8bit/Zeal-8-bit-OS/tree/main/kernel_headers/sdcc/include) respectively. +The syscalls are all documented in the header files provided for both assembly and C, you will find these header file in the `kernel_headers/` directory, check its [README file for more information](https://github.com/Zeal8bit/Zeal-8-bit-OS/tree/main/kernel_headers/README.md). ## Drivers diff --git a/kernel_headers/README.md b/kernel_headers/README.md index d34e0b4..a713679 100644 --- a/kernel_headers/README.md +++ b/kernel_headers/README.md @@ -14,10 +14,11 @@ For more info, check the `README.md` files contained inside each supported compi Currently, assembly header files are provided for the following assemblers: -* z88dk-z80asm. The directory `z88dk-z80asm` contains an assembly file that is meant to be included inside any assembly project. Of course, that project should be assembled with z88dk's `z80asm` assembler. +* z88dk-z80asm. The directory `z88dk-z80asm` contains assembly files that are meant to be included inside any z80asm assembly project. +* gnu-as. The direcotry `gnu-as` contains assembly files that are meant to be included in any assembly project that is assembled with `z80-elf` assembler. ## Examples As its name states, the directory `examples`, contains code samples that can be compiled/assembled for Zeal 8-bit OS. -For more info, check the `README.md` files contained inside each example directory. \ No newline at end of file +For more info, check the `README.md` files contained inside each example directory. \ No newline at end of file diff --git a/kernel_headers/examples/gnu-as/Makefile b/kernel_headers/examples/gnu-as/Makefile new file mode 100644 index 0000000..0ff3bff --- /dev/null +++ b/kernel_headers/examples/gnu-as/Makefile @@ -0,0 +1,49 @@ +SHELL := /bin/bash + +SRCS = main.asm +BIN = main.bin + +# Only extract the following sections from the ELF file +SECTIONS=.text .data + +# Assembler flags, provide a path to the Zeal 8-bit OS header files. The option `-g` is not mandatory, +# it will only generate more debug symbols in the final ELF file, making it possible to use utils +# like `addr2line`. It can be omitted. +ASFLAGS = -I$(ZOS_INCLUDE) -g +# The binary must be relocated at address 0x4000 since this is where Zeal 8-bit OS kernel will copy +# the program and execute it from. Make sure all the other sections follow the `.text` section. +# If not, it may be necessary to make a custom Linker Script file `.ld` and provide it at link time. +LDFLAGS = -Ttext 0x4000 + +# Directory where source files are and where the binaries will be put +INPUT_DIR = src +OUTPUT_DIR = bin +OBJS := $(addprefix $(OUTPUT_DIR)/, $(SRCS:.asm=.asm.o)) + +# Include directory containing Zeal 8-bit OS header file. +ifndef ZOS_PATH +$(error "Please define ZOS_PATH environment variable. It must point to Zeal 8-bit OS source code path.") +endif +ZOS_INCLUDE = $(ZOS_PATH)/kernel_headers/gnu-as/ + +# Assembler binary name +AS = z80-elf-as +LD = z80-elf-ld +OBJCPY = z80-elf-objcopy +.PHONY: all + +all: $(OUTPUT_DIR) $(OUTPUT_DIR)/$(BIN) + +$(OUTPUT_DIR)/$(BIN): $(OBJS) + $(LD) $(LDFLAGS) -o $@.elf $< + $(OBJCPY) $(addprefix --only-section=, $(SECTIONS)) -O binary $@.elf $@ + + +$(OUTPUT_DIR)/%.asm.o: $(INPUT_DIR)/%.asm + $(AS) $(ASFLAGS) -o $@ $< + +$(OUTPUT_DIR): + mkdir -p $@ + +clean: + rm -r bin/ \ No newline at end of file diff --git a/kernel_headers/examples/gnu-as/src/main.asm b/kernel_headers/examples/gnu-as/src/main.asm new file mode 100644 index 0000000..79a580f --- /dev/null +++ b/kernel_headers/examples/gnu-as/src/main.asm @@ -0,0 +1,58 @@ +; SPDX-FileCopyrightText: 2024 Zeal 8-bit Computer +; +; SPDX-License-Identifier: CC0-1.0 + + ; Include the Zeal 8-bit OS header file, containing all the syscalls macros. + .include "zos_sys.asm" + + ; The .text section will be linked at address `0x4000` + .text + + ; We can start the code here, directly, no need to create a routine, but let's keep it clean. + .global _start +_start: + ; Start by printing a message on the standard output. As we know at compile time the message, the length + ; and the dev we want to write on, we can use S_WRITE3 macro. + S_WRITE3 DEV_STDOUT, _message, _message_end - _message + ; Read from the input to get the user name. Let's use a 128-byte buffer, this should be more than enough. + ; We could use S_READ3, but let's use READ instead as most syscalls require us to setup the parameters. + ld h, DEV_STDIN ; Standard input dev (already opened by the kernel) + ld de, _buffer ; Destination buffer + ld bc, 128 ; Buffer size + READ() + ; Syscalls only alters the registers that contain a return value. READ() puts error in A and the number + ; of bytes/character in BC. + ; Check for errors, we can use `cp ERR_SUCCESS`, but let's optimize a bit as ERR_SUCCESS is 0. + or a + ; Exit on error + jr nz, _end + ; No error, print "Hello ", we have to add the size of "Hello " to BC + ld hl, _buffer - _hello_name + add hl, bc + ; Put the final size in BC + ld b, h + ld c, l + ; Prepare the other parameters to print: H and DE. + ; We could use S_WRITE2 here, but let's prepare the parameters manually instead. + ld h, DEV_STDOUT + ld de, _hello_name + WRITE +_end: + ; We MUST execute EXIT() syscall at the end of any program. + ; Exit code is stored in H, it is 0 if everything went fine. + ld h, a + EXIT() + ; Only used for debugging, `readelf` will show the size of this routine, not necessary + ; for runtime, can be ignore or removed. + .size _start, . - _start + + .data + ; Define a label before and after the message, so that we can get the length of the string + ; thanks to `_message_end - _message`. +_message: .ascii "Type your name: " +_message_end: + + ; Prefix the buffer with the word "Hello ", so that we can print "Hello ". +_hello_name: .ascii "Hello " + ; Buffer we will use to store the input text +_buffer: .space 128 diff --git a/kernel_headers/gnu-as/README.md b/kernel_headers/gnu-as/README.md new file mode 100644 index 0000000..2ad2e86 --- /dev/null +++ b/kernel_headers/gnu-as/README.md @@ -0,0 +1,17 @@ +# Assembly header file for z88dk's z80asm assembler + +In this directory, you will find an assembly file, `zos_sys.asm`, that acts as an header file. Indeed, it shall be included by any assembly project targeting Zeal 8-bit OS. + +This file is fairly simple, it contains macros for all the syscalls available in Zeal 8-bit OS kernel. For more info about each of them, check the header file directly. + +## Usage + +The following line needs be added at the top of the assembly file using Zeal 8-bit OS syscalls: +``` + INCLUDE "zos_sys.asm" +``` + +When assembling, either copy this file in the project's directory, either provide the following option to `z80asm`: +``` +z88dk-z80asm -I +``` diff --git a/kernel_headers/gnu-as/zos_err.asm b/kernel_headers/gnu-as/zos_err.asm new file mode 100644 index 0000000..c245cc3 --- /dev/null +++ b/kernel_headers/gnu-as/zos_err.asm @@ -0,0 +1,33 @@ +; SPDX-FileCopyrightText: 2024 Zeal 8-bit Computer +; +; SPDX-License-Identifier: Apache-2.0 + + .equiv ZOS_ERR_HEADER, 1 + + .equ ERR_SUCCESS, 0 + .equ ERR_FAILURE, 1 + .equ ERR_NOT_IMPLEMENTED, 2 + .equ ERR_NOT_SUPPORTED, 3 + .equ ERR_NO_SUCH_ENTRY, 4 + .equ ERR_INVALID_SYSCALL, 5 + .equ ERR_INVALID_PARAMETER, 6 + .equ ERR_INVALID_VIRT_PAGE, 7 + .equ ERR_INVALID_PHYS_ADDRESS, 8 + .equ ERR_INVALID_OFFSET, 9 + .equ ERR_INVALID_NAME, 10 + .equ ERR_INVALID_PATH, 11 + .equ ERR_INVALID_FILESYSTEM, 12 + .equ ERR_INVALID_FILEDEV, 13 + .equ ERR_PATH_TOO_LONG, 14 + .equ ERR_ALREADY_EXIST, 15 + .equ ERR_ALREADY_OPENED, 16 + .equ ERR_ALREADY_MOUNTED, 17 + .equ ERR_READ_ONLY, 18 + .equ ERR_BAD_MODE, 19 + .equ ERR_CANNOT_REGISTER_MORE, 20 + .equ ERR_NO_MORE_ENTRIES, 21 + .equ ERR_NO_MORE_MEMORY, 22 + .equ ERR_NOT_A_DIR, 23 + .equ ERR_NOT_A_FILE, 24 + .equ ERR_ENTRY_CORRUPTED, 25 + .equ ERR_DIR_NOT_EMPTY, 26 diff --git a/kernel_headers/gnu-as/zos_keyboard.asm b/kernel_headers/gnu-as/zos_keyboard.asm new file mode 100644 index 0000000..2d47ed0 --- /dev/null +++ b/kernel_headers/gnu-as/zos_keyboard.asm @@ -0,0 +1,178 @@ + +; SPDX-FileCopyrightText: 2024 Zeal 8-bit Computer +; +; SPDX-License-Identifier: Apache-2.0 + + .equiv ZOS_KEYBOARD_H, 1 + + ; This file represents the keyboard interface for a key input driver. + + ; kb_cmd_t: This group represents the IOCTL commands an input/keyboard driver should implement + ; Set the current input mode, check the attributes in the group below. + ; Parameters: + ; E - New mode + .equ KB_CMD_SET_MODE, 0 + ; Number of commands above + .equ KB_CMD_COUNT, 1 + + + ; kb_mode_t: Modes supported by input/keyboard driver + ; In raw mode, all the characters that are pressed or released are sent to the user + ; program when a read occurs. + ; This means that no treatment is performed by the driver whatsoever. For example, + ; if (Left) Shift and A are pressed, the bytes sent to the user program will be: + ; 0x93 0x61 + ; Left shift Ascii lower A + ; The non-special characters must be sent in lowercase mode. + .equ KB_MODE_RAW, 0 + ; In COOKED mode, the entry is buffered. So when a key is pressed, it is + ; first processed before being stored in a buffer and sent to the user + ; program (on "read"). + ; The buffer is flushed when it is full or when Enter ('\n') is pressed. + ; The keys that will be treated by the driver are: + ; - Non-special characters: + ; which includes all printable characters: letters, numbers, punctuation, etc. + ; - Special characters that have a well defined behavior: + ; which includes caps lock, (left/right) shifts, left arrow, + ; right arrow, delete key, tabulation, enter. + ; The remaining special characters are ignored. Release key events are + ; also ignored. + .equ KB_MODE_COOKED, 1 + ; HALFCOOKED mode is similar to COOKED mode, the difference is, when an + ; unsupported key is pressed, instead of being ignored, it is filled in + ; the buffer and a special error code is returned: ERR_SPECIAL_STATE + ; The "release key" events shall still be ignored and not transmitted to + ; the user program. + .equ KB_MODE_HALFCOOKED, 2 + ; Number of modes above + .equ KB_MODE_COUNT, 3 + + + ; kb_block_t: Blocking/non-blocking modes, can be ORed with the mode above + ; In blocking mode, the `read` syscall will not return until a newline character ('\n') + ; is encountered. + .equ KB_READ_BLOCK, 0 << 2 + ; In non-blocking mode, the syscall `read` can return 0 if there is no pending keys that were + ; typed by the user. Please note that the driver must NOT return KB_RELEASED without a key following it. + ; In other words, if the buffer[i] has been filled with a KB_RELEASED, buffer[i+1] must be valid + ; and contain the key that was released. + .equ KB_READ_NON_BLOCK, 1 << 2 + + + ; The following codes represent the keys of a 104-key keyboard that can be detected by + ; the keyboard driver. + ; When the input mode is set to RAW, the following keys can be sent to the + ; user program to mark which keys were pressed (or released). + .equ KB_KEY_A, 'a' + .equ KB_KEY_B, 'b' + .equ KB_KEY_C, 'c' + .equ KB_KEY_D, 'd' + .equ KB_KEY_E, 'e' + .equ KB_KEY_F, 'f' + .equ KB_KEY_G, 'g' + .equ KB_KEY_H, 'h' + .equ KB_KEY_I, 'i' + .equ KB_KEY_J, 'j' + .equ KB_KEY_K, 'k' + .equ KB_KEY_L, 'l' + .equ KB_KEY_M, 'm' + .equ KB_KEY_N, 'n' + .equ KB_KEY_O, 'o' + .equ KB_KEY_P, 'p' + .equ KB_KEY_Q, 'q' + .equ KB_KEY_R, 'r' + .equ KB_KEY_S, 's' + .equ KB_KEY_T, 't' + .equ KB_KEY_U, 'u' + .equ KB_KEY_V, 'v' + .equ KB_KEY_W, 'w' + .equ KB_KEY_X, 'x' + .equ KB_KEY_Y, 'y' + .equ KB_KEY_Z, 'z' + .equ KB_KEY_0, '0' + .equ KB_KEY_1, '1' + .equ KB_KEY_2, '2' + .equ KB_KEY_3, '3' + .equ KB_KEY_4, '4' + .equ KB_KEY_5, '5' + .equ KB_KEY_6, '6' + .equ KB_KEY_7, '7' + .equ KB_KEY_8, '8' + .equ KB_KEY_9, '9' + .equ KB_KEY_BACKQUOTE, '`' + .equ KB_KEY_MINUS, '-' + .equ KB_KEY_EQUAL, '=' + .equ KB_KEY_BACKSPACE, '\b' + .equ KB_KEY_SPACE, ' ' + .equ KB_KEY_ENTER, '\n' + .equ KB_KEY_TAB, '\t' + .equ KB_KEY_COMMA, ',' + .equ KB_KEY_PERIOD, '.' + .equ KB_KEY_SLASH, '/' + .equ KB_KEY_SEMICOLON, ';' + .equ KB_KEY_QUOTE, 0x27 + .equ KB_KEY_LEFT_BRACKET, '[' + .equ KB_KEY_RIGHT_BRACKET, ']' + .equ KB_KEY_BACKSLASH, 0x5c + + ; When the input mode is set to RAW or HALFCOOKED, the following keys can be sent to the + ; user program to mark which special keys were pressed (or released). + .equ KB_NUMPAD_0, 0x80 + .equ KB_NUMPAD_1, 0x81 + .equ KB_NUMPAD_2, 0x82 + .equ KB_NUMPAD_3, 0x83 + .equ KB_NUMPAD_4, 0x84 + .equ KB_NUMPAD_5, 0x85 + .equ KB_NUMPAD_6, 0x86 + .equ KB_NUMPAD_7, 0x87 + .equ KB_NUMPAD_8, 0x88 + .equ KB_NUMPAD_9, 0x89 + .equ KB_NUMPAD_DOT, 0x8a + .equ KB_NUMPAD_ENTER, 0x8b + .equ KB_NUMPAD_PLUS, 0x8c + .equ KB_NUMPAD_MINUS, 0x8d + .equ KB_NUMPAD_MUL, 0x8e + .equ KB_NUMPAD_DIV, 0x8f + .equ KB_NUMPAD_LOCK, 0x90 + .equ KB_SCROLL_LOCK, 0x91 + .equ KB_CAPS_LOCK, 0x92 + .equ KB_LEFT_SHIFT, 0x93 + .equ KB_LEFT_ALT, 0x94 + .equ KB_LEFT_CTRL, 0x95 + .equ KB_RIGHT_SHIFT, 0x96 + .equ KB_RIGHT_ALT, 0x97 + .equ KB_RIGHT_CTRL, 0x98 + .equ KB_HOME, 0x99 + .equ KB_END, 0x9a + .equ KB_INSERT, 0x9b + .equ KB_DELETE, 0x9c + .equ KB_PG_DOWN, 0x9d + .equ KB_PG_UP, 0x9e + .equ KB_PRINT_SCREEN, 0x9f + .equ KB_UP_ARROW, 0xa0 + .equ KB_DOWN_ARROW, 0xa1 + .equ KB_LEFT_ARROW, 0xa2 + .equ KB_RIGHT_ARROW, 0xa3 + .equ KB_LEFT_SPECIAL, 0xa4 + + .equ KB_ESC, 0xf0 + .equ KB_F1, 0xf1 + .equ KB_F2, 0xf2 + .equ KB_F3, 0xf3 + .equ KB_F4, 0xf4 + .equ KB_F5, 0xf5 + .equ KB_F6, 0xf6 + .equ KB_F7, 0xf7 + .equ KB_F8, 0xf8 + .equ KB_F9, 0xf9 + .equ KB_F10, 0xfa + .equ KB_F11, 0xfb + .equ KB_F12, 0xfc + + ; When a released event is triggered, this value shall precede the key concerned. + ; As such, in RAW mode, each key press should at some point generate a release + ; sequence. For example: + ; 0x61 [...] 0xFE 0x61 + ; A [...] A released + .equ KB_RELEASED, 0xfe + .equ KB_UNKNOWN, 0xff diff --git a/kernel_headers/gnu-as/zos_serial.asm b/kernel_headers/gnu-as/zos_serial.asm new file mode 100644 index 0000000..3f847c8 --- /dev/null +++ b/kernel_headers/gnu-as/zos_serial.asm @@ -0,0 +1,39 @@ + +; SPDX-FileCopyrightText: 2024 Zeal 8-bit Computer +; +; SPDX-License-Identifier: Apache-2.0 + + .equiv ZOS_SERIAL_H, 1 + + ; This file represents the minimal interface for a serial driver. + ; If any command below is not supported, the driver should return + ; ERR_NOT_SUPPORTED. + + ; IOCTL commands the driver should implement + ; Get the serial driver attributes. + ; Parameter: + ; DE - Address to fill with the 16-bit attribute + .equ SERIAL_CMD_GET_ATTR, 0x80 ; See attribute group below + ; Set the serial driver attributes. + ; Parameter: + ; DE - 16-bit attributes to set (NOT AN ADDRESS/POINTER) + .equ SERIAL_CMD_SET_ATTR, 0x81 + .equ SERIAL_CMD_GET_BAUDRATE, 0x82 + .equ SERIAL_CMD_SET_BAUDRATE, 0x83 + .equ SERIAL_GET_TIMEOUT, 0x84 + .equ SERIAL_SET_TIMEOUT, 0x85 + .equ SERIAL_GET_BLOCKING, 0x86 + .equ SERIAL_SET_BLOCKING, 0x87 + ; Number of commands above + .equ SERIAL_CMD_COUNT, 0x88 + + + ; Serial driver attribute bitmap to use with SERIAL_CMD_GET_ATTR command + .equ SERIAL_ATTR_MODE_RAW, 1 << 0 + .equ SERIAL_ATTR_RSVD1, 1 << 1 + .equ SERIAL_ATTR_RSVD2, 1 << 2 + .equ SERIAL_ATTR_RSVD3, 1 << 3 + .equ SERIAL_ATTR_RSVD4, 1 << 4 + .equ SERIAL_ATTR_RSVD5, 1 << 5 + .equ SERIAL_ATTR_RSVD6, 1 << 6 + .equ SERIAL_ATTR_RSVD7, 1 << 7 diff --git a/kernel_headers/gnu-as/zos_sys.asm b/kernel_headers/gnu-as/zos_sys.asm new file mode 100644 index 0000000..72b261e --- /dev/null +++ b/kernel_headers/gnu-as/zos_sys.asm @@ -0,0 +1,625 @@ +; SPDX-FileCopyrightText: 2024 Zeal 8-bit Computer +; +; SPDX-License-Identifier: Apache-2.0 + + .include "zos_err.asm" + + .equiv ZOS_SYS_HEADER, 1 + + ; @brief Opened device value for the standard output + .equ DEV_STDOUT, 0 + + ; @brief Opened device value for the standard input + .equ DEV_STDIN, 1 + + ; @brief Maximum length for a file/directory name + .equ FILENAME_LEN_MAX, 16 + + ; @brief Maximum length for a path + .equ PATH_MAX, 128 + + ; @note In the syscalls below, any pointer, buffer or structure address + ; provided with an explicit or implicit (sizeof structure) size must NOT + ; cross virtual page boundary, and must not be bigger than a virtual page + ; size. + ; For example, if we have two virtual pages located at 0x4000 and 0x8000 + ; respectively, a buffer starting a 0x7F00 cannot be used with a size of + ; more than 256 bytes in the function below. Indeed, if the size is bigger, + ; the end of buffer would cross the second page, which starts at 0x8000. In + ; such cases, two or more calls to the desired syscall must be performed. + + ; @brief Flags used to defined the modes to use when opening a file + ; Note on the behavior: + ; - O_RDONLY: Can only read + ; - O_WRONLY: Can only write + ; - O_RDWR: Can both read and write, sharing the same cursor, writing will + ; - overwrite existing data. + ; - O_APPEND: Needs writing. Before each write, the cursor will be + ; - moved to the end of the file, as if seek was called. + ; - So, if used with O_RDWR, reading after a write will read 0. + ; - O_TRUNC: No matter if O_RDWR or O_WRONLY, the size is first set to + ; - 0 before any other operation occurs. + .equ O_WRONLY_BIT, 0 + .equ O_RDONLY, 0 << O_WRONLY_BIT + .equ O_WRONLY, 1 << O_WRONLY_BIT + .equ O_RDWR , 2 + .equ O_TRUNC , 1 << 2 + .equ O_APPEND, 2 << 2 + .equ O_CREAT , 4 << 2 + ; Only makes sense for drivers, not files + .equ O_NONBLOCK, 1 << 5 + + ; @brief Directory entry size, in bytes. + ; Its content would be represented like this in C: + ; struct { + ; uint8_t d_flags; + ; char d_name[FILENAME_LEN_MAX]; + ; } + .equ ZOS_DIR_ENTRY_SIZE, 1 + FILENAME_LEN_MAX + + ; @brief Date structure size, in bytes. + ; Its content would be represented like this in C: + ; struct { + ; uint16_t d_year; + ; uint8_t d_month; + ; uint8_t d_day; + ; uint8_t d_date; // Range [1,7] (Sunday, Monday, Tuesday...) + ; uint8_t d_hours; + ; uint8_t d_minutes; + ; uint8_t d_seconds; + ; } + ; All the fields above are in BCD format. + .equ ZOS_DATE_SIZE, 17 + + ; @brief Stat file size, in bytes. + ; Its content would be represented like this in C: + ; struct { + ; uint32_t s_size; // in bytes + ; zos_date_t s_date; + ; char s_name[FILENAME_LEN_MAX]; + ; } + .equ ZOS_STAT_SIZE, 1 + ZOS_DATE_SIZE + FILENAME_LEN_MAX + + ; @brief Whence values. Check `seek` syscall for more info + .equ SEEK_SET, 0 + .equ SEEK_CUR, 1 + .equ SEEK_END, 2 + + ; @brief Filesystems supported on Zeal 8-bit OS + .equ FS_RAWTABLE, 0 + + ; @brief Kernel configuration structure size, in bytes. + ; Its content would be represented like this in C: + ; struct { + ; uint8_t c_target; // Machine number the OS is running on, 0 means UNKNOWN + ; uint8_t c_mmu; // 0 if the MMU-less kernel is running, 1 else + ; char c_def_disk; // Upper case letter for the default disk + ; uint8_t c_max_driver; // Maximum number of driver loadable in the kernel + ; uint8_t c_max_dev; // Maximum number of opened devices in the kernel + ; uint8_t c_max_files; // Maximum number of opened files in the kernel + ; uint16_t c_max_path; // Maximum path length + ; void* c_prog_addr; // Virtual address where user programs are loaded + ; void* c_custom; // Custom area, target-specific + ; } zos_config_t; + .equ ZOS_CONFIG_SIZE, 12 + + ; @brief Override caller program when invoking `exec` + .equ EXEC_OVERRIDE_PROGRAM, 0 + + ; @brief Keep the caller program in memory when invoking `exec`, until "child" + ; program finishes its execution + .equ EXEC_PRESERVE_PROGRAM, 1 + + + ; @brief .macro to abstract the syscall instruction + .macro SYSCALL + rst 0x8 + .endm + + + ; @brief Read from an opened device. + ; Can be invoked with READ(). + ; + ; Parameters: + ; H - Device to read from. This value must point to an opened device. + ; Refer to `open()` for more info. + ; DE - Buffer to store the bytes read from the opened device. + ; BC - Size of the buffer passed, maximum size is a page size. + ; Returns: + ; A - ERR_SUCCESS on success, error value else + ; BC - Number of bytes filled in DE. + .macro READ _ + ld l, 0 + SYSCALL + .endm + + + ; @brief Helper for the READ syscall when the opened dev value is known + ; at assembly it. + ; Can be invoked with S_READ1(dev). + ; Refer to READ() syscall for more info about the parameters and the returned values. + .macro S_READ1 dev + ld h, \dev + READ() + .endm + + ; @brief Helper for the READ syscall when the opened dev and the buffer are known + ; at assembly it. + ; Can be invoked with S_READ2(dev, buf). + ; Refer to READ() syscall for more info about the parameters and the returned values. + .macro S_READ2 dev, buf + ld h, \dev + ld de, \buf + READ() + .endm + + ; @brief Helper for the READ syscall when the opened dev, the buffer and the size + ; are known at assembly it. + ; Can be invoked with S_READ3(dev, buf, size). + ; Refer to READ() syscall for more info about the parameters and the returned values. + .macro S_READ3 dev, buf, len + ld h, \dev + ld de, \buf + ld bc, \len + READ() + .endm + + + ; @brief Write to an opened device. + ; Can be invoked with WRITE(). + ; + ; Parameters: + ; H - Number of the dev to write to. + ; DE - Buffer to write to. The buffer must NOT cross page boundary. + ; BC - Size of the buffer passed. Maximum size is a page size. + ; Returns: + ; A - ERR_SUCCESS on success, error value else + ; BC - Number of bytes written + .macro WRITE _ + ld l, 1 + SYSCALL + .endm + + + ; @brief Helper for the WRITE syscall when the opened dev value is known + ; at assembly it. + ; Can be invoked with S_WRITE1(dev). + ; Refer to WRITE() syscall for more info about the parameters and the returned values. + .macro S_WRITE1 dev + ld h, \dev + WRITE() + .endm + + ; @brief Helper for the WRITE syscall when the opened dev and the buffer are known + ; at assembly it. + ; Can be invoked with S_WRITE2(dev, buf). + ; Refer to WRITE() syscall for more info about the parameters and the returned values. + .macro S_WRITE2 dev, str + ld h, \dev + ld de, \str + WRITE() + .endm + + ; @brief Helper for the WRITE syscall when the opened dev, the buffer and the size + ; are known at assembly it. + ; Can be invoked with S_WRITE3(dev, buf, size). + ; Refer to WRITE() syscall for more info about the parameters and the returned values. + .macro S_WRITE3 dev, str, len + ld h, \dev + ld de, \str + ld bc, \len + WRITE() + .endm + + + ; @brief Open the given file or driver. + ; Drivers name shall not exceed 4 characters and must be preceded by # (5 characters in total) + ; Names not starting with # will be considered as files. + ; Path to the file to open, the path can be: + ; - Relative to the current directory: file.txt + ; - Absolute to the current disk: /path/to/file.txt + ; - Absolute to the system: C:/path/to/file.txt + ; Can be invoked with OPEN(). + ; + ; Parameters: + ; BC - Name: driver or file. + ; H - Flags, can be O_RDWR, O_RDONLY, O_WRONLY, O_NONBLOCK, O_CREAT, O_APPEND, etc... + ; It is possible to OR them. + ; Returns: + ; A - Number of the newly opened dev on success, negated error value else. + .macro OPEN _ + ld l, 2 + SYSCALL + .endm + + + ; @brief Close an opened device. It is necessary to keep the least minimum + ; of devices/files opened as the limit is set by the kernel and may be + ; different between implementations. + ; Can be invoked with CLOSE(). + ; + ; Parameters: + ; H - Number of the dev to close + ; Returns: + ; A - ERR_SUCCESS on success, error code else + .macro CLOSE _ + ld l, 3 + SYSCALL + .endm + + + ; @brief Return the stats of an opened file. + ; The returned structure is defined above, check ZOS_STAT_SIZE description. + ; Can be invoked with DSTAT(). + ; + ; Parameters: + ; H - Opened dev to get the stat of. + ; DE - Address of the stat structure to fill on success. + ; The memory pointed must be big enough to store the file information. + ; Returns: + ; A - ERR_SUCCESS on success, error else + .macro DSTAT _ + ld l, 4 + SYSCALL + .endm + + + ; @brief Return the stats of a file. + ; The returned structure is defined above, check ZOS_STAT_SIZE description. + ; Can be invoked with STAT(). + ; + ; Parameters: + ; BC - Path to the file. + ; DE - Address of the stat structure to fill on success. + ; The memory pointed must be big enough to store the file information. + ; Returns: + ; A - ERR_SUCCESS on success, error else + .macro STAT _ + ld l, 5 + SYSCALL + .endm + + + ; @brief Move the cursor of an opened file or an opened driver. + ; In case of a driver, the implementation is driver-dependent. In case of + ; a file, the cursor never moves further than the file size. + ; If the given whence is SEEK_SET, and the given offset is bigger than the file, + ; the cursor will be set to the end of the file. + ; Similarly, if the whence is SEEK_END and the given offset is positive, + ; the cursor won't move further than the end of the file. + ; Can be invoked with SEEK(). + ; + ; Parameters: + ; H - Opened dev to reposition the cursor from, must refer to an opened driver. + ; BCDE - 32-bit offset, signed if whence is SEEK_CUR/SEEK_END. + ; Unsigned if SEEK_SET. + ; A - Whence. When set to SEEK_SET, `offset` parameter is the new value of + ; cursor in the file. + ; When set to SEEK_CUR, `offset` represents a signed value to add to the + ; current position in the file. + ; When set to SEEK_END, `offset` represents a signed value to add to the + ; last valid position in the file. + ; Returns: + ; A - ERR_SUCCESS on success, error code else. + ; BCDE - Unsigned 32-bit offset. Resulting file offset. + .macro SEEK _ + ld l, 6 + SYSCALL + .endm + + + ; @brief Perform an input/output operation on an opened driver. + ; The command and parameter are specific to the device drivers of destination. + ; Make sure to check the documentation of the driver buffer calling this function. + ; Can be invoked with IOCTL(). + ; + ; Parameters: + ; H - Dev number, must refer to an opened driver, not a file. + ; C - Command number. This is driver-dependent, check the driver documentation for more info. + ; DE - 16-bit parameter. This is also driver dependent. This can be used as a 16-bit value or + ; as an address. Similarly to the buffers in `read` and `write` routines, if this is an + ; address, it must not cross a page boundary. + ; Returns: + ; A - ERR_SUCCESS on success, error code else + .macro IOCTL _ + ld l, 7 + SYSCALL + .endm + + + ; @brief Create a directory at the specified location. + ; If one of the directories in the given path doesn't exist, this will fail. + ; For example, if mkdir("A:/D/E/F") is requested where D exists but E doesn't, this syscall + ; will fail and return an error. + ; Can be invoked with MKDIR(). + ; + ; Parameters: + ; DE - Path of the directory to create, including the NULL-terminator. Must NOT cross boundaries. + ; Returns: + ; A - ERR_SUCCESS on success, error code else. + .macro MKDIR _ + ld l, 8 + SYSCALL + .endm + + + ; @brief Change the current working directory path. + ; Can be invoked with CHDIR(). + ; + ; Parameters: + ; DE - Path to the new working directory. The string must be NULL-terminated. + ; Returns: + ; A - ERR_SUCCESS on success, error code else. + .macro CHDIR _ + ld l, 9 + SYSCALL + .endm + + + ; @brief Get the current working directory. + ; Can be invoked with CURDIR(). + ; + ; Parameters: + ; DE - Buffer to store the current path to. The buffer must be big enough + ; to store a least PATH_MAX bytes. + ; Returns: + ; A - ERR_SUCCESS on success, error code else + .macro CURDIR _ + ld l, 10 + SYSCALL + .endm + + + ; @brief Open a directory given a path. + ; The path can be relative, absolute to the disk or absolute to the + ; system, just like open for files. + ; Can be invoked with OPENDIR(). + ; + ; Parameters: + ; DE - Path to the directory to open, the string must be NULL-terminated. + ; Like for all the above paths, it can be: + ; * Relative to the current directory ("../dir", "dir1") + ; * Absolute to the disk ("/dir1/dir2") + ; * Absolute to the system ("A:/dir1") + ; Returns: + ; A - Number for the newly opened dev on success, negated error value else. + .macro OPENDIR _ + ld l, 11 + SYSCALL + .endm + + + ; @brief Read the next entry from the given opened directory. + ; Can be invoked with READDIR(). + ; + ; Parameters: + ; H - Number of the dev to write to. If the given dev is not a directory, + ; an error will be returned. + ; DE - Buffer to store the entry data, the buffer must NOT cross page boundary. + ; It must be big enough to hold at least ZOS_DIR_ENTRY_SIZE bytes. + ; Returns: + ; A - ERR_SUCCESS on success, + ; ERR_NO_MORE_ENTRIES if all the entries have been browsed already, + ; error value else. + .macro READDIR _ + ld l, 12 + SYSCALL + .endm + + + ; @brief Remove a file or an empty directory. + ; Can be invoked with RM(). + ; + ; Parameters: + ; DE - Path to the file or directory to remove. Like the path above, it must be + ; NULL-terminated, can be a relative, relative to the disk, or absolute path. + ; Returns: + ; A - ERR_SUCCESS on success, error code else. + .macro RM _ + ld l, 13 + SYSCALL + .endm + + + ; @brief Mount a new disk, given a driver, a letter and a file system. + ; The letter assigned to the disk must not be in use. + ; Can be invoked with MOUNT(). + ; + ; Parameters: + ; H - Opened dev number. It must be an opened driver, not a file. The dev can be closed + ; after mounting, this will not affect the mounted disk. + ; D - ASCII letter to assign to the disk (upper or lower) + ; E - File system, check the FS_* .macro defined at the top of this file. + ; Returns: + ; A - ERR_SUCCESS on success, error code else + .macro MOUNT _ + ld l, 14 + SYSCALL + .endm + + + ; @brief Exit the program and give back the hand to the kernel. If the caller program invoked + ; EXEC() with `EXEC_PRESERVE_PROGRAM` as the mode, it will be reloaded from RAM after + ; exiting the current program. + ; Can be invoked with EXIT(). + ; + ; Parameters: + ; H - Return code to pass to caller program + ; Returns: + ; None + .macro EXIT _ + ld l, 15 + SYSCALL + .endm + + + ; @brief Load and execute a program from a file name given as a parameter. + ; The current program, invoking this syscall, can either be preserved in memory + ; (only available when the kernel is compiled with MMU support!) + ; until the sub-program finishes executing, or, it can be covered/overridden. + ; In the first case, upon return, D register will contain the return value of the + ; sub-program. + ; The depth of sub-programs is defined and limited in the kernel. As such, it is not + ; guaranteed that it will always be possible to execute a sub-program while keeping + ; the current one in memory. It depends on the target and kernel configuration. + ; Can be invoked with EXEC(). + ; + ; Parameters: + ; BC - File to load and execute. The string must be NULL-terminated and must not cross boundaries. + ; DE - String argument to give to the program to execute, must be NULL-terminated. Can be NULL. + ; H - Mode marking whether the current program shall be preserved in RAM or overwritten by sub-program. + ; Can be either `EXEC_OVERRIDE_PROGRAM` or `EXEC_PRESERVE_PROGRAM`. + ; Returns: + ; A - When invoked with `EXEC_OVERRIDE_PROGRAM`, returns only on error + ; When invoked with `EXEC_PRESERVE_PROGRAM`, returns ERR_SUCCESS when the sub-program was executed + ; successfully (regardless of its returned value), error code else. If error code is ERR_CANNOT_REGISTER_MORE, + ; the maximum depth has been reached, the current program cannot execute a program while being preserved in + ; memory. In that case, it shall either exit, either execute the sub-program with `EXEC_OVERRIDE_PROGRAM`. + ; D - Sub-program exit value, when invoked with `EXEC_PRESERVE_PROGRAM`. + ; Alters: + ; Contrarily to other syscalls, invoking EXEC() will not preserve AF, BC, DE, IX, IY. + ; Only HL is guaranteed to be preserved. + .macro EXEC _ + ld l, 16 + SYSCALL + .endm + + + ; @brief Duplicate the given opened dev to a new index. This will only + ; duplicate the "pointer" to the actual implementation. For example, + ; duplicate the dev of an opened file and performing a seek on the it + ; will affect both the old and the new dev. + ; This can be handy to override the standard input or output. + ; Can be invoked with DUP(). + ; + ; Parameters: + ; H - Dev number to duplicate. + ; E - New number for the opened dev. + ; Returns: + ; A - ERR_SUCCESS on success, error code else + .macro DUP _ + ld l, 17 + SYSCALL + .endm + + + ; @brief Sleep for a specified duration. + ; Can be invoked with MSLEEP(). + ; + ; Parameters: + ; DE - 16-bit duration (maximum 65 seconds). + ; Returns: + ; A - ERR_SUCCESS on success, error code else. + .macro MSLEEP _ + ld l, 18 + SYSCALL + .endm + + + ; @brief Routine to manually set/reset the time counter, in milliseconds. + ; Can be invoked SETTIME(). + ; + ; Parameters: + ; H - ID of the clock (unused for now) + ; DE - 16-bit counter value, in milliseconds. + ; Returns: + ; A - ERR_SUCCESS on success, ERR_NOT_IMPLEMENTED if target doesn't implement + ; this feature error code else. + .macro SETTIME _ + ld l, 19 + SYSCALL + .endm + + + ; @brief Get the time counter, in milliseconds. + ; The granularity is dependent on the implementation/hardware, for example, + ; it could be 1ms, 2ms, 16ms, etc. + ; You should be aware of this when calling this syscall. + ; Can be invoked with GETTIME(). + ; + ; Parameters: + ; H - Id of the clock (for future use, unused for now) + ; Returns: + ; A - ERR_SUCCESS on success, ERR_NOT_IMPLEMENTED if target doesn't implement + ; this feature, error code else. + ; DE - 16-bit counter value, in milliseconds. + .macro GETTIME _ + ld l, 20 + SYSCALL + .endm + + + ; @brief Set the system date, on targets where RTC is available. + ; Can be invoked with SETDATE(). + ; + ; Parameters: + ; DE - Address of the date structure, as defined at the top of this file. + ; The buffer must NOT cross page boundary. + ; Returns: + ; A - ERR_SUCCESS on success, ERR_NOT_IMPLEMENTED if target doesn't implement + ; this feature, error code else + .macro SETDATE _ + ld l, 21 + SYSCALL + .endm + + + ; @brief Get the system date, on targets where RTC is available. + ; Can be invoked with GETDATE(). + ; + ; Parameters: + ; DE - Buffer to store the date structure in, the buffer must NOT cross page boundary. + ; It must be big enough to hold at least ZOS_DATE_SIZE bytes. + ; Returns: + ; A - ERR_SUCCESS on success, ERR_NOT_IMPLEMENTED if target doesn't implement + ; this feature, error code else + .macro GETDATE _ + ld l, 22 + SYSCALL + .endm + + + ; @brief Map a physical address/region to a virtual address/region. + ; Can be invoked with MAP(). + ; + ; Parameters: + ; DE - Destination address in virtual memory. This will be rounded down to the target closest + ; page bound. For example, passing 0x5000 here, would in fact trigger a remap of the + ; page starting at 0x4000 on a target that has 16KB virtual pages. + ; HBC - Upper 24-bits of the physical address to map. If the target does not support + ; the physical address given, an error will be returned. + ; Similarly to the virtual address, the value may be rounded down to the closest page bound. + ; Returns: + ; ERR_SUCCESS on success, error code else. + .macro MAP _ + ld l, 23 + SYSCALL + .endm + + + ; @brief Swap the given opened devs. This can be handy to temporarily override the + ; standard input or output and restore it afterwards. + ; Can be invoked with SWAP(). + ; + ; Parameters: + ; H - First dev number. + ; E - Second dev number. + ; Returns: + ; A - ERR_SUCCESS on success, error code else + .macro SWAP _ + ld l, 24 + SYSCALL + .endm + + + ; @brief Get a read-only pointer to the kernel configuration. + ; + ; Parameters: + ; PAIR - 16-bit register (HL, DE, BC) to store the configuration structure address in + ; Returns: + ; PAIR - Address of the configuration structure. It is guaranteed that the structure won't + ; be spread across two 256-byte pages. In other words, it is possible to browse the + ; structure by performing 8-bit arithmetic (`inc l` for example). + .macro KERNEL_CONFIG PAIR + ld \PAIR, (0x0004) + .endm diff --git a/kernel_headers/gnu-as/zos_video.asm b/kernel_headers/gnu-as/zos_video.asm new file mode 100644 index 0000000..6209385 --- /dev/null +++ b/kernel_headers/gnu-as/zos_video.asm @@ -0,0 +1,88 @@ + +; SPDX-FileCopyrightText: 2024 Zeal 8-bit Computer +; +; SPDX-License-Identifier: Apache-2.0 + + .equiv ZOS_VIDEO_H, 1 + + ; This file represents the minimal interface for a video driver that supports + ; text mode. It is not mandatory to support all the IOCTL commands presented + ; below. If any is not supported, the driver shall return ERR_NOT_SUPPORTED + ; IOCTL commands the driver should implement + ; Get the video driver capabilities, such as the supported modes, the supported + ; colors, scrolling, etc... + ; TODO: Define the attributes. + .equ CMD_GET_ATTR, 0 ; See attribute structure + + ; Get the area bounds of the current display mode + ; Parameter: + ; DE - Address of area_t structure (defined below) + ; It will be filled by the driver. + .equ CMD_GET_AREA, 1 + + ; Get the current position (X,Y) of the cursor. They represent an index, + ; so they start at 0. + ; Parameter: + ; DE - Address of a 2-byte array. First byte shall be filled with X, second + ; byte shall be filled with Y coordinate. + .equ CMD_GET_CURSOR_XY, 2 + + ; Set the (non-constant) attributes. + .equ CMD_SET_ATTR, 3 + + ; Set the (X,Y) position of the cursor. If the given coordinate is out of bounds, + ; the driver can either return an error or accept it and adjust it to the end + ; of line/column/screen. + ; Parameters: + ; D - X coordinate + ; E - Y coordinate + .equ CMD_SET_CURSOR_XY, 4 + + ; Set the current background and foreground color for the text that is going to + ; be written. This does NOT affect the text already written. The colors must be + ; taken from the TEXT_COLOR_* group defined below. + ; If a color is not supported, the driver can either return an error, or take a + ; color similar to the one requested. + ; Parameters: + ; D - Background color + ; E - Foreground color + .equ CMD_SET_COLORS, 5 + + ; Clear the screen and reposition the cursor at the top left. + .equ CMD_CLEAR_SCREEN, 6 + + ; Resets the screen to the same state as on boot up + .equ CMD_RESET_SCREEN, 7 + + ; Number of commands above + .equ CMD_COUNT 8 + + + ; List of colors to pass to CMD_SET_COLORS command. + ; This corresponds to the 4-bit VGA palette. + .equ TEXT_COLOR_BLACK, 0x0 + .equ TEXT_COLOR_DARK_BLUE, 0x1 + .equ TEXT_COLOR_DARK_GREEN, 0x2 + .equ TEXT_COLOR_DARK_CYAN, 0x3 + .equ TEXT_COLOR_DARK_RED, 0x4 + .equ TEXT_COLOR_DARK_MAGENTA, 0x5 + .equ TEXT_COLOR_BROWN, 0x6 + .equ TEXT_COLOR_LIGHT_GRAY, 0x7 + .equ TEXT_COLOR_DARK_GRAY, 0x8 + .equ TEXT_COLOR_BLUE, 0x9 + .equ TEXT_COLOR_GREEN, 0xa + .equ TEXT_COLOR_CYAN, 0xb + .equ TEXT_COLOR_RED, 0xc + .equ TEXT_COLOR_MAGENTA, 0xd + .equ TEXT_COLOR_YELLOW, 0xe + .equ TEXT_COLOR_WHITE, 0xf + + + ; area_t structure, used when getting the current mode area + .equ area_width_t, 0 ; Width of the screen in the current mode + .equ area_height_t, 1 ; Height of the screen in the current mode + .equ area_count_t, 2 ; Number of entities on-screen (usually, width * height) + .equ area_end_t, 4 + + + ENDIF ; ZOS_VIDEO_H