commit 7993f4b88fa3b8bc98769046d43b97ef5fa7448d Author: Yann MAGNIN Date: Sat Sep 25 16:02:50 2021 +0200 vxBoot (0.0.1) - First commit @add <> terminal device <> builtin - ls (file listing), help (some information message)) <> smemfs - SMEM file system abstraction <> small CLI to interact with the core diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2d33e55 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Build files +/build-fx +/build-cg +/*.g1a +/*.g3a + +# Python bytecode + __pycache__/ + +# Common IDE files +*.sublime-project +*.sublime-workspace +.vscode +commit.txt diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e27990d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.15) +project(vxBoot) + +include(GenerateG1A) +include(GenerateG3A) +include(Fxconv) +find_package(Gint 2.1 REQUIRED) + +set(SOURCES + src/main.c + src/cli/parser.c + src/fs/smemfs/mount.c + src/fs/smemfs/pread.c + src/terminal/close.c + src/terminal/open.c + src/terminal/read.c + src/terminal/util.c + src/terminal/write.c + src/builtin/ls.c + src/builtin/help.c +) +# Shared assets, fx-9860G-only assets and fx-CG-50-only assets +set(ASSETS + # ... +) +set(ASSETS_fx + # ... +) +set(ASSETS_cg + # ... +) + +fxconv_declare_assets(${ASSETS} ${ASSETS_fx} ${ASSETS_cg} WITH_METADATA) + +add_executable(vxBoot ${SOURCES} ${ASSETS} ${ASSETS_${FXSDK_PLATFORM}}) +target_compile_options(vxBoot PRIVATE -Wall -Wextra -Os) +target_include_directories(vxBoot PRIVATE include/) +target_link_libraries(vxBoot Gint::Gint) + +if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G) + generate_g1a(TARGET vxBoot OUTPUT "vxBoot.g1a" + NAME "vxBoot" ICON assets-fx/icon.png) +elseif("${FXSDK_PLATFORM_LONG}" STREQUAL fxCG50) + generate_g3a(TARGET vxBoot OUTPUT "vxBoot.g3a" + NAME "vxBoot" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png) +endif() diff --git a/assets-cg/icon-sel.png b/assets-cg/icon-sel.png new file mode 100644 index 0000000..7137b50 Binary files /dev/null and b/assets-cg/icon-sel.png differ diff --git a/assets-cg/icon-uns.png b/assets-cg/icon-uns.png new file mode 100644 index 0000000..3c99f62 Binary files /dev/null and b/assets-cg/icon-uns.png differ diff --git a/assets-fx/icon.png b/assets-fx/icon.png new file mode 100644 index 0000000..c92f12a Binary files /dev/null and b/assets-fx/icon.png differ diff --git a/include/vxBoot/builtin.h b/include/vxBoot/builtin.h new file mode 100644 index 0000000..7cf1456 --- /dev/null +++ b/include/vxBoot/builtin.h @@ -0,0 +1,11 @@ +#ifndef __VXBOOT_BUILTIN_H__ +# define __VXBOOT_BUILTIN_H__ + +#include +#include + +extern int ls_main(int argc, char **argv); +extern int os_main(int argc, char **argv); +extern int help_main(int argc, char **argv); + +#endif /*__VXBOOT_BUILTIN_H__*/ diff --git a/include/vxBoot/cli.h b/include/vxBoot/cli.h new file mode 100644 index 0000000..1d48b27 --- /dev/null +++ b/include/vxBoot/cli.h @@ -0,0 +1,10 @@ +#ifndef __VXBOOT_PARSER_H__ +# define __VXBOOT_PARSER_H__ + +#include +#include + +extern int cli_parser_strtotab(int *argc, char ***argv, char const *str); +extern void cli_parser_strtotab_quit(int *argc, char ***argv); + +#endif /*__VXBOOT_PARSER_H__*/ diff --git a/include/vxBoot/fs/smemfs.h b/include/vxBoot/fs/smemfs.h new file mode 100644 index 0000000..a71340e --- /dev/null +++ b/include/vxBoot/fs/smemfs.h @@ -0,0 +1,52 @@ +#ifndef __VXBOOT_FS_SMEMFS_H__ +# define __VXBOOT_FS_SMEMFS_H__ + +#include +#include + +#include + +//---- +// Internal structure used to provide Casio's syscall abstraction +//--- + +/* Internal superblock use for the Casio's syscall abstraction */ +struct smemfs_superblock { + struct smemfs_inode *root_inode; + struct smemfs_inode *fake_root_inode; +}; + +/* Internal struct used to store SMEM dump */ +struct smemfs_inode { + /* File name */ + char name[32]; + + /* Internal file's information */ + int type; + size_t fsize; + size_t dsize; + + /* Internal abstraction information */ + struct smemfs_inode *child; + struct smemfs_inode *sibling; + struct smemfs_inode *parent; +}; + +//--- +// Dev symbols +//--- +#define SMEMFS_FAKE_ROOT_INODE ((void*)0xdeadbeef) +extern struct smemfs_superblock smemfs_superblock; + + +//--- +// Primitives +//--- +extern struct smemfs_inode *smemfs_mount(void); + +extern ssize_t smemfs_pread(struct smemfs_inode *inode, + void *buf, size_t count, off_t pos); + +extern struct smemfs_inode *smemfs_alloc_inode(void); + +#endif /*__VXBOOT_FS_SMEMFS_H__*/ diff --git a/include/vxBoot/terminal.h b/include/vxBoot/terminal.h new file mode 100644 index 0000000..3465646 --- /dev/null +++ b/include/vxBoot/terminal.h @@ -0,0 +1,79 @@ +#ifndef __VXBOOT_TERMINAL_H__ +# define __VXBOOT_TERMINAL_H__ + +#include +#include +#include + +/* internal terminal hardcoded information */ +#define TERM_PRIVATE_WATERMARK (0xdeadbeef) +#define TERM_RDBUFFER_NBLINE (32) + +/* hardcoded font size (TODO: dynamic !) */ +#ifdef FXCG50 +#define FWIDTH 8 +#define FHEIGHT 9 +#endif +#ifdef FX9860G +#define FWIDTH 5 +#define FHEIGHT 7 +#endif + +/* define terminal structure */ +struct terminal { + /* windows information */ + struct { + unsigned short ws_col; + unsigned short ws_row; + unsigned short ws_xpixel; + unsigned short ws_ypixel; + unsigned short ft_xpixel; + unsigned short ft_ypixel; + } winsize; + + /* cursor information */ + struct { + unsigned short x; + unsigned short y; + } cursor; + + /* buffer information */ + struct { + uint8_t *data; + off_t cursor; + size_t size; + } buffer; + + /* private information */ + struct { + struct { + int id; + } timer; + uint32_t watermark; + struct { + int fg; + int bg; + } color; + } private; +}; + +/* define the terminal */ +extern struct terminal terminal; + +//--- +// User interface +//--- +extern int terminal_open(void); +extern int terminal_write(const char *format, ...); +extern int terminal_read(void *buffer, size_t nb); +extern int terminal_close(void); + +//--- +// Internal interface +//--- +extern void terminal_clear(void); +extern int terminal_cursor_handler(void); +extern void terminal_buffer_display(void); +extern void terminal_buffer_insert(char *buffer, size_t nb); + +#endif /*__FXBOOT_TERMINAL_H__*/ diff --git a/src/builtin/help.c b/src/builtin/help.c new file mode 100644 index 0000000..0500d77 --- /dev/null +++ b/src/builtin/help.c @@ -0,0 +1,21 @@ +#include "vxBoot/builtin.h" +#include "vxBoot/terminal.h" + +int help_main(int argc, char **argv) +{ + //TODO : use arguments ? + (void)argc; + (void)argv; + + terminal_write( +"VxBoot was created by Yatis for the sole purpose of being able to load Vhex -" +" an experimental operating system touching many sensitive things on the " +"machine.\n" +"\n" +"The available commands are:\n" +" help - display this help message\n" +" ls - list files in storage memory\n" +" os - allows you to interact with the bootstraper\n" + ); + return (0); +} diff --git a/src/builtin/ls.c b/src/builtin/ls.c new file mode 100644 index 0000000..903b9c3 --- /dev/null +++ b/src/builtin/ls.c @@ -0,0 +1,78 @@ +#include "vxBoot/builtin.h" +#include "vxBoot/terminal.h" +#include "vxBoot/fs/smemfs.h" + +#include + +#include + +/* ls_help() : Display the hep message */ +static void ls_help(void) +{ + terminal_write( +"NAME\n" +" ls - list directory contents\n" +"\n" +"SYNOPSIS\n" +" ls [-h|--help]\n" +"\n" +"DESCRIPTION\n" +" List file information about FILEs (from the root directory only). The" +" display is inspired from the utilitary `tree` in UNIX environment.\n" +"\n" +" -h,--help\n" +" Display this help message\n" + ); +} + +/* inode_walk() : walk onto the filesystem and display files */ +static void inode_walk(struct smemfs_inode *inode, int level, uint32_t bitmap) +{ + const char *records; + + if (inode == NULL) + return; + + /* handle indentation */ + for (int i = 0; i < level; ++i) { + records = "\t"; + if ((bitmap & (1 << i)) != 0) + records = "|\t"; + terminal_write(records); + } + + /* handle file name and sibling dependencies */ + records = "|-- (%x) %s"; + bitmap |= 1 << level; + if (inode->sibling == NULL) { + records = "`-- (%x) %s"; + bitmap &= ~(1 << level); + } + terminal_write(records, inode->type, inode->name); + + /* handle file type */ + if (inode->type == BFile_Type_Directory) { + terminal_write(":\n"); + inode_walk(inode->child, level + 1, bitmap); + inode_walk(inode->sibling, level, bitmap); + return; + } + terminal_write("\n"); + inode_walk(inode->sibling, level, bitmap); +} + +/* ls_main() : entry of the "ls" builtin */ +int ls_main(int argc, char **argv) +{ + if (smemfs_superblock.fake_root_inode != SMEMFS_FAKE_ROOT_INODE) { + terminal_write("smemfs not mounted !\n"); + return (84); + } + if (argc > 1 && strcmp(argv[1], "-help") == 0) { + ls_help(); + return (0); + } + terminal_write("/:\n"); + inode_walk(smemfs_superblock.root_inode, 0, 0x00000000); + return (0); +} diff --git a/src/cli/parser.c b/src/cli/parser.c new file mode 100644 index 0000000..a535a45 --- /dev/null +++ b/src/cli/parser.c @@ -0,0 +1,140 @@ +#include "vxBoot/cli.h" + +#include +#include + +/* define external symbols */ +extern int cli_parser_strtotab(int *argc, char ***argv, char const *str); +extern void cli_parser_strtotab_quit(int *argc, char ***argv); + +/* parser_get_word(): Get the word at the current cursor location. */ +static int parser_get_word(char ***tab, size_t *tab_pos, + char const *str, int *counter) +{ + int i; + + /* skip until separator */ + i = -1; + while (str[++i] != '\0' + && str[i] != '\n' + && str[i] != ' ' + && str[i] != '\t'); + + /* dump the word of needed */ + if (*tab != NULL) { + (*tab)[*tab_pos] = (char*)calloc(1, i + 1); + if ((*tab)[*tab_pos] == NULL) + return (-1); + memset((*tab)[*tab_pos], 0, i + 1); + strncpy((*tab)[(*tab_pos)++], str, i); + } + + /* update the internal counter */ + (*counter)++; + return (i); +} + +/* parser_get_inibitor() + + This function will get the content of an inhibitor (and check if the + inhibitor characteres are alone or not). */ +static int parser_get_inibitor(char ***tab, size_t *tab_pos, + char const *str, int *counter) +{ + int i; + + /* get the inibitor end */ + i = 0; + while (str[++i] != '\0' && str[i] != '\"'); + if (str[i] != '\"') + return (0); + + /* dump the word if needed */ + if (*tab != NULL) { + (*tab)[*tab_pos] = (char*)calloc(1, i + 1); + if ((*tab)[*tab_pos] == NULL) + return (-1); + memset((*tab)[*tab_pos], 0, i + 1); + strncpy((*tab)[(*tab_pos)++], str + 1, i - 1); + } + + /* update the internal counter */ + (*counter)++; + return (i + 1); +} + +/* parser_setup_arg() + + This function removes useless spaces, tabs and handle '\"' inhibitor. + Return the number of word(s) stored in "str". */ +static int parser_entry(char ***tab, char const *str) +{ + size_t tab_pos; + int counter; + int sz; + + str--; + sz = 0; + counter = 0; + tab_pos = 0; + while (*(++str) != '\0' && *str != '\n' && sz >= 0) { + if (*str == '\"') { + sz = parser_get_inibitor(tab, &tab_pos, str, &counter); + if (sz > 0) { + str = &str[sz]; + continue; + } + } + if (*str != ' ' && *str != '\t') { + sz = parser_get_word(tab, &tab_pos, str, &counter) - 1; + if (sz > 0) + str = &str[sz]; + } + } + return (counter); +} + +/* cli_parser_strtotab() + Generate word table and indicated the number of word find in the string. */ +int cli_parser_strtotab(int *argc, char ***argv, char const *str) +{ + if (argc == NULL || argv == NULL || str == NULL) + return (-1); + + /* Get the number of word. */ + *argv = NULL; + *argc = parser_entry(argv, str); + if (*argc <= 0) + return (-2); + + *argv = (char **)calloc(1, sizeof(char *) * (*argc + 1)); + if (*argv == NULL) + return (-3); + + /* Dump all word. */ + if (parser_entry(argv, str) != *argc) { + cli_parser_strtotab_quit(argc, argv); + return (-4); + } + (*argv)[*argc] = NULL; + return (0); +} + +/* cli_parser_strtotab_quit() + Free all allocated memory generated by "strtotab()" */ +void cli_parser_strtotab_quit(int *argc, char ***argv) +{ + if (argc == NULL || argv == NULL) + return; + if (*argv == NULL) { + *argc = 0; + return; + } + while (--(*argc) >= 0) { + if ((*argv)[*argc] != NULL) + free((*argv)[*argc]); + } + free(*argv); + *argv = NULL; + *argc = 0; +} diff --git a/src/fs/smemfs/mount.c b/src/fs/smemfs/mount.c new file mode 100644 index 0000000..4325e7c --- /dev/null +++ b/src/fs/smemfs/mount.c @@ -0,0 +1,117 @@ +#include "vxBoot/fs/smemfs.h" +#include "vxBoot/terminal.h" + +#include +#include +#include + +#include +#include + +/* Define the super block information */ +struct smemfs_superblock smemfs_superblock = { + .root_inode = NULL, + .fake_root_inode = NULL +}; + +/* wide_char_convert(): convert wide character to ASCII format */ +static size_t wide_char_convert(char *pathname, uint16_t *pathname_wc) +{ + size_t i; + + i = -1; + while (pathname_wc[++i] != 0x0000 && pathname_wc[i] != 0xffff) + pathname[i] = pathname_wc[i] & 0x00ff; + pathname[i] = '\0'; + return (i); +} + +/* dump_smem_level(): Dump one level of the SMEM FileSystem */ +static void dump_smem_level(struct smemfs_inode *parent, + struct smemfs_inode **sibling, + uint16_t *path, off_t cursor) +{ + struct BFile_FileInfo file_info; + struct smemfs_inode *inode; + uint16_t buffer[32]; + int handle; + + /* Generate searching path: + This format is used by the `Bfile_Find*()` syscall */ + if (parent != NULL) { + for (int j = 0; parent->name[j] != '\0'; ++j) + path[cursor++] = (uint16_t)(parent->name[j]); + path[cursor++] = '\\'; + } + path[cursor + 0] = '*'; + path[cursor + 1] = 0x0000; + + /* Find the first file: + The search buffer and the buffer which will content the file name is + the same. But it's not used at the same time so we can use this + tricky way to save some stack space. */ + if (BFile_FindFirst(path, &handle, buffer, &file_info) != 0) + return; + inode = NULL; + do { + /* Try to alloc new inode */ + *sibling = calloc(1, sizeof(struct smemfs_inode)); + if (*sibling == NULL) + break; + + /* Save the first inode (used after for directories checking) */ + if (inode == NULL) + inode = *sibling; + + /* Convert wide char to ASCII */ + wide_char_convert((*sibling)->name, buffer); + + /* Dump file informations */ + (*sibling)->type = file_info.type; + (*sibling)->fsize = file_info.file_size; + (*sibling)->dsize = file_info.data_size; + + /* Link node and get next sibling */ + (*sibling)->parent = parent; + sibling = &(*sibling)->sibling; + + /* try to find the next file information */ + } while (BFile_FindNext(handle, buffer, &file_info) == 0); + BFile_FindClose(handle); + + /* Now let's check all files to find directories */ + while (inode != NULL) { + if (inode->type == BFile_Type_Directory) + dump_smem_level(inode, &inode->child, path, cursor); + inode = inode->sibling; + } +} + +/* smemfs_mount(): Mount the file system + + We only use internal Casio's syscall, so this dummy primitive can + be ported easly. */ +struct smemfs_inode *smemfs_mount(void) +{ + uint16_t buffer[512]; + void *root_inode; + + root_inode = smemfs_superblock.root_inode; + if (root_inode == NULL) { + /* Generate fake root inode */ + smemfs_superblock.fake_root_inode = SMEMFS_FAKE_ROOT_INODE; + + /* Dump SMEM files organisation */ + smemfs_superblock.root_inode = NULL; + memcpy(buffer, u"\\\\fls0\\", 14); + dump_smem_level(smemfs_superblock.root_inode, + &smemfs_superblock.root_inode, + buffer, 7); + + /* Get the "fake" root inode */ + root_inode = smemfs_superblock.fake_root_inode; + } + + /* Return the sector table to simulate the root inode. */ + return (root_inode); +} diff --git a/src/fs/smemfs/pread.c b/src/fs/smemfs/pread.c new file mode 100644 index 0000000..a0de81a --- /dev/null +++ b/src/fs/smemfs/pread.c @@ -0,0 +1,77 @@ +#include "vxBoot/fs/smemfs.h" + +#include +#include + +#include + +/* internal struct */ +struct __file_info { + uint16_t pathname[256]; + void *buf; + size_t count; + off_t pos; +}; + +/* generate_abolute_path(): Generate abolute path + + This function will generate the absolute path of a file because Casio's open + primitive doesn't handle cannonical path */ +static void generate_absolute_path(uint16_t *pathname, + struct smemfs_inode *inode, int *pos) +{ + if (inode == NULL) { + memcpy(pathname, u"\\\\fls0", 12); + *pos = 6; + return; + } + + generate_absolute_path(pathname, inode->parent, pos); + + pathname[(*pos)++] = '\\'; + for (int i = 0; inode->name[i] != '\0'; ) { + pathname[*pos] = inode->name[i]; + *pos = *pos + 1; + i = i + 1; + } + pathname[*pos] = '\0'; +} + +/* __smemfs_pread() : involved in Casio's world */ +static void __smemfs_pread(struct __file_info *info, ssize_t *read) +{ + int handle; + + *read = -1; + handle = BFile_Open(info->pathname, BFile_ReadOnly); + if (handle >= 0) { + *read = BFile_Read(handle, info->buf, info->count, info->pos); + BFile_Close(handle); + } +} + +/* smemfs_read(): Read primitive */ +ssize_t smemfs_pread(struct smemfs_inode *inode, + void *buf, size_t count, off_t pos) +{ + struct __file_info file_info = { + .buf = buf, + .count = count, + .pos = pos + }; + ssize_t read; + int tmp; + + if (inode == NULL) + return (-1); + + tmp = 0; + generate_absolute_path(file_info.pathname, inode, &tmp); + + gint_world_switch(GINT_CALL( + (void*)&__smemfs_pread, + (void*)&file_info, &read + )); + + return (read); +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..b756317 --- /dev/null +++ b/src/main.c @@ -0,0 +1,81 @@ +#include "vxBoot/terminal.h" +#include "vxBoot/cli.h" +#include "vxBoot/builtin.h" +#include "vxBoot/fs/smemfs.h" + +#include +#include + +#include + +//TODO: better API for the command-line parser + +/* internal builtin list */ +struct { + const char *name; + int (*f)(int argc, char **argv); +} cmd_list[] = { + {.name = "ls", &ls_main}, + {.name = "os", NULL}, + {.name = "help", &help_main}, + {.name = NULL, NULL} +}; + +/* try to find the appropriate command */ +static int (*check_cmd(char *cmd))(int, char**) +{ + for (int i = 0; cmd_list[i].name != NULL; ++i) { + if (strcmp(cmd, cmd_list[i].name) != 0) + continue; + if (cmd_list[i].f == NULL) + terminal_write("command exist but not implemented\n"); + return (cmd_list[i].f); + } + return (NULL); +} + +/* entry of the bootloader */ +int main(void) +{ + int (*builtin)(int, char**); + const char *usrline; + char buff[128]; + char **argv; + int argc; + int ret; + + /* automated hook */ + /* TODO: better way to execute early command */ + smemfs_mount(); + + ret = 0; + terminal_open(); + terminal_write("Welcome to vxBoot, the bootstrapper for the Vhex kernel!\n"); + terminal_write("Type `help` for instruction on how to use vxBoot\n"); + while (1) { + /* get user command */ + usrline = (ret != 0) ? "/[%d]>" : "/>"; + terminal_write(usrline, ret); + ret = 0; + if (terminal_read(buff, 128) <= 1) + continue; + + /* parse and try to find the command */ + if (cli_parser_strtotab(&argc, &argv, buff) != 0) { + terminal_write("error when processing \"%s\"", buff); + ret = 255; + continue; + } + builtin = check_cmd(argv[0]); + if (builtin == NULL) { + terminal_write("command \"%s\" not found\n", argv[0]); + ret = 127; + continue; + } + + /* execute the command and free'd allocated memories */ + ret = builtin(argc, argv); + cli_parser_strtotab_quit(&argc, &argv); + } + return (1); +} diff --git a/src/terminal/close.c b/src/terminal/close.c new file mode 100644 index 0000000..01ec2c9 --- /dev/null +++ b/src/terminal/close.c @@ -0,0 +1,18 @@ +#include "vxBoot/terminal.h" + +#include + +#include + +/* terminal_close(): Uninitialize the terminal */ +int terminal_close(void) +{ + if (terminal.private.watermark != TERM_PRIVATE_WATERMARK) + return (-1); + if (terminal.private.timer.id >= 0) + timer_stop(terminal.private.timer.id); + if (terminal.buffer.data != NULL) + free(terminal.buffer.data); + terminal.private.watermark = TERM_PRIVATE_WATERMARK; + return (0); +} diff --git a/src/terminal/open.c b/src/terminal/open.c new file mode 100644 index 0000000..0c7b7b3 --- /dev/null +++ b/src/terminal/open.c @@ -0,0 +1,47 @@ +#include "vxBoot/terminal.h" + +#include +#include + +#include +#include + +/* internal symbols */ +struct terminal terminal; + +/* terminal_open(): Initialize and open the terminal */ +int terminal_open(void) +{ + if (terminal.private.watermark == TERM_PRIVATE_WATERMARK) + terminal_close(); + memset(&terminal, 0x00, sizeof(struct terminal)); + terminal.private.timer.id = -1; + terminal.winsize.ws_xpixel = DWIDTH; + terminal.winsize.ws_ypixel = DHEIGHT; + terminal.winsize.ft_xpixel = FWIDTH + 1; + terminal.winsize.ft_ypixel = FHEIGHT + 1; + terminal.winsize.ws_col = DWIDTH / terminal.winsize.ft_xpixel; + terminal.winsize.ws_row = DHEIGHT / terminal.winsize.ft_ypixel; + terminal.buffer.size = terminal.winsize.ws_row + * terminal.winsize.ws_col + * TERM_RDBUFFER_NBLINE + * sizeof(uint8_t); + terminal.buffer.data = calloc(1, terminal.buffer.size); + if (terminal.buffer.data == NULL) { + terminal_close(); + return (-1); + } + terminal.private.timer.id = timer_configure( + TIMER_ANY, + 250000, + GINT_CALL(terminal_cursor_handler) + ); + if (terminal.private.timer.id < 0) { + terminal_close(); + return (-1); + } + terminal.private.color.bg = C_BLACK; + terminal.private.color.fg = C_WHITE; + terminal.private.watermark = TERM_PRIVATE_WATERMARK; + return (0); +} diff --git a/src/terminal/read.c b/src/terminal/read.c new file mode 100644 index 0000000..6a312f2 --- /dev/null +++ b/src/terminal/read.c @@ -0,0 +1,245 @@ +#include "vxBoot/terminal.h" + +#include +#include +#include + +#include + +/* internal structure used to store many information */ +struct { + struct { + unsigned short x; + unsigned short y; + int visible; + off_t saved; + } cursor; + struct { + uint8_t alpha: 1; + uint8_t shift: 1; + uint8_t ctrl: 1; + uint8_t exit: 1; + uint8_t const: 4; + } mode; + struct { + size_t max; + size_t size; + off_t cursor; + char *data; + } buffer; +} term_rdinfo; + +//--- +// +//--- +void term_display_all(void) +{ + /* stop the timer too avoid interrupt-loop */ + if (terminal.private.timer.id >= 0) + timer_pause(terminal.private.timer.id); + + /* mark special characte that the cursor is here */ + if (term_rdinfo.cursor.visible == 1) + term_rdinfo.buffer.data[term_rdinfo.buffer.cursor] |= 0x80; + + /* restore terminal context */ + terminal.cursor.x = term_rdinfo.cursor.x; + terminal.cursor.y = term_rdinfo.cursor.y; + terminal.buffer.cursor = term_rdinfo.cursor.saved; + terminal_buffer_insert(term_rdinfo.buffer.data, + term_rdinfo.buffer.size); + + /* display management */ + dclear(terminal.private.color.bg); + terminal_buffer_display(); + dupdate(); + + /* remove cursor mark */ + term_rdinfo.buffer.data[term_rdinfo.buffer.cursor] &= ~0x80; + + /* restart the timer */ + if (terminal.private.timer.id >= 0) + timer_start(terminal.private.timer.id); +} + +//--- +// Callback function +//--- + +int terminal_cursor_handler(void) +{ + term_rdinfo.cursor.visible ^= 1; + term_display_all(); + return (0); +} + +//--- +// buffer functions +//--- + +/* term_buffer_remove(): Remove character based on current cursor position */ +static void term_buffer_remove(void) +{ + /* check if this action is possible */ + if (term_rdinfo.buffer.cursor == 0) + return; + /* move data if needed */ + if (term_rdinfo.buffer.cursor < term_rdinfo.buffer.size - 1) { + memcpy( + &term_rdinfo.buffer.data[term_rdinfo.buffer.cursor - 1], + &term_rdinfo.buffer.data[term_rdinfo.buffer.cursor], + term_rdinfo.buffer.size - term_rdinfo.buffer.cursor + ); + } + /* force NULL-char and update cursor/size */ + term_rdinfo.buffer.cursor = term_rdinfo.buffer.cursor - 1; + term_rdinfo.buffer.data[--term_rdinfo.buffer.size - 1] = '\0'; +} + +/* term_buffer_insert() - Insert character based on current cursor position */ +static int term_buffer_insert(char n) +{ + /* save space for the "\n\0" (EOL) */ + if (term_rdinfo.buffer.size + 1 >= term_rdinfo.buffer.max) + return (-1); + /* move data if needed */ + if (term_rdinfo.buffer.cursor < term_rdinfo.buffer.size - 1) { + off_t i = term_rdinfo.buffer.size + 1; + while (--i >= term_rdinfo.buffer.cursor) { + term_rdinfo.buffer.data[i] = + term_rdinfo.buffer.data[i - 1]; + } + } + /* insert the character and force NULL-char */ + term_rdinfo.buffer.data[term_rdinfo.buffer.cursor++] = n; + term_rdinfo.buffer.data[++term_rdinfo.buffer.size] = '\0'; + return (0); +} + +//--- +// key handling +//--- + +// TODO +// - F_UP -> history +// - F_DOWN -> history +static int term_key_handle_special(key_event_t key_event) +{ + switch (key_event.key) { + case KEY_SHIFT: term_rdinfo.mode.shift ^= 1; return (1); + case KEY_ALPHA: term_rdinfo.mode.alpha ^= 1; return (1); + case KEY_OPTN: term_rdinfo.mode.ctrl ^= 1; return (1); + case KEY_DOT: term_buffer_insert(' '); return (1); + case KEY_DEL: term_buffer_remove(); return (1); + case KEY_EXE: + /* Add End Of Line character */ + term_rdinfo.buffer.data[term_rdinfo.buffer.size - 1] = '\n'; + term_rdinfo.buffer.data[term_rdinfo.buffer.size] = '\0'; + + /* indicate that the EXE key has been pressed. */ + term_rdinfo.mode.exit = 1; + return (1); + case KEY_LEFT: + if (term_rdinfo.buffer.cursor > 0) + term_rdinfo.buffer.cursor -= 1; + return (1); + case KEY_RIGHT: + if (term_rdinfo.buffer.cursor < term_rdinfo.buffer.size - 1) + term_rdinfo.buffer.cursor += 1; + return (1); + default: + return (0); + } +} + +/* term_buffer_update() - Update the internal buffer with the given key code */ +static int term_key_buffer_update(key_event_t key_event) +{ + static const uint8_t keylist_alpha[] = { + KEY_XOT, KEY_LOG, KEY_LN, KEY_SIN, KEY_COS, KEY_TAN, + KEY_FRAC, KEY_FD, KEY_LEFTP, KEY_RIGHTP, KEY_COMMA, KEY_ARROW, + KEY_7, KEY_8, KEY_9, + KEY_4, KEY_5, KEY_6, KEY_MUL, KEY_DIV, + KEY_1, KEY_2, KEY_3, KEY_PLUS, KEY_MINUS, + KEY_0, 0xff + }; + static const uint8_t keylist_num[] = { + KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, + KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, + KEY_PLUS, KEY_MINUS, KEY_MUL, KEY_DIV, + KEY_LEFTP, KEY_RIGHTP, KEY_COMMA, KEY_POWER, + KEY_DOT, KEY_FD, KEY_ARROW, 0xff + }; + static const char keylist_num_char[] = "0123456789+-x/(),^.|_"; + const uint8_t *keycode_list; + char character; + int i; + + /* Get the appropriate key list. */ + keycode_list = keylist_alpha; + if (term_rdinfo.mode.shift == 1) + keycode_list = keylist_num; + + /* Try to find the pressed key. */ + i = -1; + while (keycode_list[++i] != 0xff && keycode_list[i] != key_event.key); + if (keycode_list[i] != key_event.key) + return (0); + + /* handle mode then update the buffer */ + if (term_rdinfo.mode.shift == 0) { + character = 'a' + i; + if (term_rdinfo.mode.alpha == 1) + character = 'A' + i;; + } else { + character = keylist_num_char[i]; + } + term_buffer_insert(character); + return (1); +} + + + +//--- +// primitive +//--- +int terminal_read(void *buffer, size_t nb) +{ + key_event_t key; + + /* check obvious error */ + if (buffer == NULL || nb == 0) + return (0); + + /* initialize internal data */ + memset(&term_rdinfo, 0x00, sizeof(term_rdinfo)); + memset(buffer, 0x00, nb); + + /* save terminal information */ + term_rdinfo.cursor.saved = terminal.buffer.cursor; + term_rdinfo.cursor.x = terminal.cursor.x; + term_rdinfo.cursor.y = terminal.cursor.y; + term_rdinfo.buffer.data = buffer; + term_rdinfo.buffer.size = 1; + term_rdinfo.buffer.max = nb; + + /* start cursor blink timer */ + if (terminal.private.timer.id >= 0) + timer_start(terminal.private.timer.id); + + /* keyboard handling */ + while (term_rdinfo.mode.exit == 0) { + /* handle pressed keys */ + key = getkey_opt(GETKEY_REP_ALL | GETKEY_MENU, NULL); + if (term_key_handle_special(key) == 0) + term_key_buffer_update(key); + + /* display */ + term_display_all(); + } + + /* stop the timer */ + if (terminal.private.timer.id >= 0) + timer_pause(terminal.private.timer.id); + return (term_rdinfo.buffer.size); +} diff --git a/src/terminal/util.c b/src/terminal/util.c new file mode 100644 index 0000000..0b10883 --- /dev/null +++ b/src/terminal/util.c @@ -0,0 +1,168 @@ +#include "vxBoot/terminal.h" + +#include + +#include + + +//--- +// Update the internal buffer +//--- + +/* terminal_buffer_insert() : insert string anywhere in the output buffer */ +void terminal_buffer_insert(char *buffer, size_t nb) +{ + size_t dump; + void *start; + + /* calculate the "real" number of byte to dump into the buffer */ + dump = nb; + start = &buffer[0]; + if (dump > terminal.buffer.size) { + dump -= terminal.buffer.size; + start = &buffer[nb - dump]; + } + + /* dump the buffer (be carful with the circular effect) */ + if (terminal.buffer.cursor + dump > terminal.buffer.size) { + memcpy( + &terminal.buffer.data[terminal.buffer.cursor], + start, + terminal.buffer.size - terminal.buffer.cursor + ); + dump -= terminal.buffer.size - terminal.buffer.cursor; + terminal.buffer.cursor = 0; + } + memcpy(&terminal.buffer.data[terminal.buffer.cursor], start, dump); + terminal.buffer.cursor += dump; +} + + +//--- +// Display the internal buffer +//--- + +/* terminal_vertical_update() - Update vertical cursor */ +static void terminal_vertical_update(void) +{ + if (terminal.cursor.y + 1 < terminal.winsize.ws_col) { + terminal.cursor.y = terminal.cursor.y + 1; + return; + } +} + +/* earlyterm_horizontal_update() - Update horizotal cursor */ +static int terminal_horizontal_update(void) +{ + terminal.cursor.x = terminal.cursor.x + 1; + if (terminal.cursor.x >= terminal.winsize.ws_col) { + terminal_vertical_update(); + terminal.cursor.x = 0; + return (1); + } + return (0); +} + +/* line_discipline() - Check "special" char */ +static int terminal_line_discipline(char n) +{ + int offset; + + switch (n) { + case '\0': + return (1); + case '\n': + terminal.cursor.x = 0; + terminal_vertical_update(); + return (1); + case '\b': + if (terminal.cursor.x > 0) + terminal.cursor.x = terminal.cursor.x - 1; + return (1); + case '\v': + terminal_vertical_update(); + return (1); + case '\r': + terminal.cursor.x = 0; + return (1); + case '\t': + /* Check if we need a new line or not. */ + offset = terminal.cursor.x - ((terminal.cursor.x / 5) * 5); + offset = 5 - offset; + while (--offset >= 0) + terminal_horizontal_update(); + return (1); + default: + return (0); + } +} + +/* terminal_buffer_write() : display the buffer on screen */ +void terminal_buffer_display(void) +{ + uint8_t *buffer; + uint16_t tmp; + int cursor; + int x; + int y; + int i; + + /* Due to potential special char, we sould find the "real" starting + index for the internal buffer */ + terminal.cursor.x = 0; + terminal.cursor.y = 0; + i = terminal.buffer.cursor - 1; + buffer = &terminal.buffer.data[0]; + while (1) { + /* decrease the cursor and avoid circular effect */ + if (--i < 0) + i = terminal.buffer.size - 1; + + /* check loop condition */ + if (i == (int)terminal.buffer.cursor) + break; + + /* check EOL */ + if (buffer[i] == '\0') { + break; + } + + /* handle the character (only to force update cursors) */ + if (terminal_line_discipline(buffer[i] & 0x7f) == 0) + terminal_horizontal_update(); + if (terminal.cursor.y >= terminal.winsize.ws_row) + break; + } + + /* Display character per character because we need to check special + behaviour (like cariege return, line feed, ...) */ + terminal.cursor.x = 0; + terminal.cursor.y = 0; + while (1) { + /* update the index */ + if (++i >= (int)terminal.buffer.size) + i = 0; + if (i == (int)terminal.buffer.cursor) + break; + + /* get the cursor and remove the potential cursor marker */ + cursor = ((buffer[i] & 0x80) != 0); + buffer[i] &= 0x7f; + + /* display part (character + cursor if needed) */ + x = terminal.cursor.x * terminal.winsize.ft_xpixel; + y = terminal.cursor.y * terminal.winsize.ft_ypixel; + if (terminal_line_discipline(buffer[i]) == 0) { + tmp = buffer[i] << 8; + dtext(x, y, terminal.private.color.fg, (void*)&tmp); + terminal_horizontal_update(); + } + if (cursor != 0) { + dline(x, + y + terminal.winsize.ft_ypixel, + x + terminal.winsize.ft_xpixel - 2, + y + terminal.winsize.ft_ypixel, + terminal.private.color.fg); + } + } +} diff --git a/src/terminal/write.c b/src/terminal/write.c new file mode 100644 index 0000000..6cb8f9b --- /dev/null +++ b/src/terminal/write.c @@ -0,0 +1,29 @@ +#include "vxBoot/terminal.h" + +#include + +#include +#include +#include + +/* terminal_write() - printf wrapper for the terminal device */ +int terminal_write(const char *format, ...) +{ + char buffer[1024]; + va_list ap; + int nb; + + /* process the format */ + va_start(ap, format); + nb = vsnprintf(buffer, 1024, format, ap); + va_end(ap); + + /* update the internal buffer */ + terminal_buffer_insert(buffer, nb); + + /* display the internal buffer */ + dclear(terminal.private.color.bg); + terminal_buffer_display(); + dupdate(); + return (nb); +}