diff --git a/src/libc/__fileioc_stdio.h b/src/libc/__fileioc_stdio.h new file mode 100644 index 000000000..4c38315d7 --- /dev/null +++ b/src/libc/__fileioc_stdio.h @@ -0,0 +1,45 @@ +#ifndef __FILEIOC_STDIO_H +#define __FILEIOC_STDIO_H + +#include +#include +#include +#include + +/* + * fd mapping: + * STDIN_FILENO = 0 + * STDOUT_FILENO = 1 + * STDERR_FILENO = 2 + * fileioc slot 1 = 3 + * fileioc slot 2 = 4 + * fileioc slot 3 = 5 + * fileioc slot 4 = 6 + * fileioc slot 5 = 7 + */ + +__BEGIN_DECLS + +extern FILE _file_streams[FOPEN_MAX]; + +#define FILEIOC_MIN_FD_SLOT 3 +#define FILEIOC_MAX_FD_SLOT (FILEIOC_MIN_FD_SLOT + (FOPEN_MAX - 1)) + +static inline int fileioc_slot_to_fd(ti_var_t slot) +{ + return (int)(slot + (FILEIOC_MIN_FD_SLOT - 1)); +} + +static inline ti_var_t fd_to_fileioc_slot(int fd) +{ + return (ti_var_t)(fd - (FILEIOC_MIN_FD_SLOT - 1)); +} + +static inline bool is_fd_a_fileioc_slot(int fd) +{ + return (fd >= FILEIOC_MIN_FD_SLOT && fd <= FILEIOC_MAX_FD_SLOT); +} + +__END_DECLS + +#endif /* __FILEIOC_STDIO_H */ diff --git a/src/libc/clearerr.c b/src/libc/clearerr.c index e6e0d9013..7f0847a60 100644 --- a/src/libc/clearerr.c +++ b/src/libc/clearerr.c @@ -1,7 +1,4 @@ -#include -#include - -extern FILE _file_streams[FOPEN_MAX]; +#include "__fileioc_stdio.h" void __attribute__((weak)) clearerr(FILE *stream) { diff --git a/src/libc/fclose.c b/src/libc/fclose.c index 34d2768dd..f513a9c41 100644 --- a/src/libc/fclose.c +++ b/src/libc/fclose.c @@ -1,7 +1,4 @@ -#include -#include - -extern FILE _file_streams[FOPEN_MAX]; +#include "__fileioc_stdio.h" int __attribute__((weak)) fclose(FILE *stream) { diff --git a/src/libc/feof.c b/src/libc/feof.c index 74f3f9de9..51b385126 100644 --- a/src/libc/feof.c +++ b/src/libc/feof.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" int __attribute__((weak)) feof(FILE *stream) { diff --git a/src/libc/ferror.c b/src/libc/ferror.c index 0fc44bfa3..8b97b3620 100644 --- a/src/libc/ferror.c +++ b/src/libc/ferror.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" int __attribute__((weak)) ferror(FILE *stream) { diff --git a/src/libc/fflush.c b/src/libc/fflush.c index 7fd422257..c6af6054d 100644 --- a/src/libc/fflush.c +++ b/src/libc/fflush.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" int __attribute__((weak)) fflush(FILE *stream) { diff --git a/src/libc/fgetc.c b/src/libc/fgetc.c index 6a0f395c7..a744f97ab 100644 --- a/src/libc/fgetc.c +++ b/src/libc/fgetc.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" int __attribute__((weak)) fgetc(FILE *stream) { diff --git a/src/libc/fgets.c b/src/libc/fgets.c index bd39b4200..34a4f823c 100644 --- a/src/libc/fgets.c +++ b/src/libc/fgets.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" char* __attribute__((weak)) fgets(char *__restrict str, int num, FILE *__restrict stream) { diff --git a/src/libc/fileno.c b/src/libc/fileno.c new file mode 100644 index 000000000..73404e501 --- /dev/null +++ b/src/libc/fileno.c @@ -0,0 +1,28 @@ +#include "__fileioc_stdio.h" +#include +#include + +int __attribute__((weak)) fileno(FILE *stream) +{ + if (stream == NULL) + { + // invalid stream + errno = EBADF; + return -1; + } + + if (stream == stdin) + { + return STDIN_FILENO; + } + if (stream == stdout) + { + return STDOUT_FILENO; + } + if (stream == stderr) + { + return STDERR_FILENO; + } + + return fileioc_slot_to_fd(stream->slot); +} diff --git a/src/libc/fopen.c b/src/libc/fopen.c index 2b0b56ccd..91c66333c 100644 --- a/src/libc/fopen.c +++ b/src/libc/fopen.c @@ -1,10 +1,6 @@ -#include +#include "__fileioc_stdio.h" -#include #include -#include - -extern FILE _file_streams[FOPEN_MAX]; FILE* __attribute__((weak)) fopen(const char *__restrict filename, const char *__restrict mode) { diff --git a/src/libc/fputc.c b/src/libc/fputc.c index 78dc905ed..c9deaa1b6 100644 --- a/src/libc/fputc.c +++ b/src/libc/fputc.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" int __attribute__((weak)) fputc(int c, FILE *stream) { diff --git a/src/libc/fputs.c b/src/libc/fputs.c index f476ad945..495735d3a 100644 --- a/src/libc/fputs.c +++ b/src/libc/fputs.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" int __attribute__((weak)) fputs(const char *__restrict str, FILE *__restrict stream) { diff --git a/src/libc/fread.c b/src/libc/fread.c index 4fdac1179..ca8abaf78 100644 --- a/src/libc/fread.c +++ b/src/libc/fread.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" size_t __attribute__((weak)) fread(void *ptr, size_t size, size_t count, FILE *__restrict stream) { diff --git a/src/libc/fseek.c b/src/libc/fseek.c index 8f551c4b8..1b205e3d7 100644 --- a/src/libc/fseek.c +++ b/src/libc/fseek.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" int __attribute__((weak)) fseek(FILE *stream, long int offset, int origin) { diff --git a/src/libc/ftell.c b/src/libc/ftell.c index 05b116c8a..1e19ed4a4 100644 --- a/src/libc/ftell.c +++ b/src/libc/ftell.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" #include long int __attribute__((weak)) ftell(FILE *stream) diff --git a/src/libc/fwrite.c b/src/libc/fwrite.c index 90b299159..ad02a7b74 100644 --- a/src/libc/fwrite.c +++ b/src/libc/fwrite.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" size_t __attribute__((weak)) fwrite(const void *__restrict ptr, size_t size, size_t count, FILE *__restrict stream) { diff --git a/src/libc/include/stdio.h b/src/libc/include/stdio.h index d64f1c028..3ff03fe76 100644 --- a/src/libc/include/stdio.h +++ b/src/libc/include/stdio.h @@ -81,6 +81,8 @@ int remove(const char *filename); int rename(const char *old_filename, const char *new_filename); +int fileno(FILE *stream); + /* standard impls */ int getchar(void); diff --git a/src/libc/include/unistd.h b/src/libc/include/unistd.h new file mode 100644 index 000000000..d2d92278e --- /dev/null +++ b/src/libc/include/unistd.h @@ -0,0 +1,30 @@ +#ifndef _UNISTD_H +#define _UNISTD_H + +#include + +/* + * unsigned int sleep(unsigned int seconds); + * typedef unsigned int useconds_t; + * int usleep(useconds_t usec); + */ +#include + +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + +#ifndef _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED +typedef __PTRDIFF_TYPE__ ssize_t; +#endif /* _SSIZE_T_DEFINED */ + +__BEGIN_DECLS + +int isatty(int fd); + +void swab(const void *__restrict src, void *__restrict dst, ssize_t count); + +__END_DECLS + +#endif /* _UNISTD_H */ diff --git a/src/libc/isatty.c b/src/libc/isatty.c new file mode 100644 index 000000000..584507f8d --- /dev/null +++ b/src/libc/isatty.c @@ -0,0 +1,20 @@ +#include "__fileioc_stdio.h" +#include +#include + +int __attribute__((weak)) isatty(int fd) +{ + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) + { + return 1; + } + if (is_fd_a_fileioc_slot(fd)) + { + // not a terminal + errno = ENOTTY; + return 0; + } + // invalid fd + errno = EBADF; + return 0; +} diff --git a/src/libc/remove.c b/src/libc/remove.c index e7234d578..1432c4512 100644 --- a/src/libc/remove.c +++ b/src/libc/remove.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" int __attribute__((weak)) remove(const char *filename) { diff --git a/src/libc/rename.c b/src/libc/rename.c index 67fc9e2ec..ca9c849e8 100644 --- a/src/libc/rename.c +++ b/src/libc/rename.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" __attribute__((__weak__)) int rename(const char *old_filename, const char *new_filename) { diff --git a/src/libc/rewind.c b/src/libc/rewind.c index 6b8b37332..ab42d43c7 100644 --- a/src/libc/rewind.c +++ b/src/libc/rewind.c @@ -1,5 +1,4 @@ -#include -#include +#include "__fileioc_stdio.h" void __attribute__((weak)) rewind(FILE *stream) { diff --git a/src/libc/swab.src b/src/libc/swab.src new file mode 100644 index 000000000..453eef582 --- /dev/null +++ b/src/libc/swab.src @@ -0,0 +1,72 @@ + .assume adl=1 + + .section .text + + .global _swab + .type _swab, @function + +.if 1 + +; void swab(const void *__restrict src, void *__restrict dst, ssize_t count) +_swab: + ; swab has unspecified behaviour when count is odd: + ; - this implementation treats an odd count as if it were count - 1 + ; - does nothing when count <= 1 or count is negative + ld iy, 0 + add iy, sp + sra (iy + 11) + ret m ; do nothing when negative + ld bc, (iy + 9) + rr b + rr c + ; BC = count / 2 + sbc hl, hl + adc hl, bc + ret z ; count <= 1 + ld de, (iy + 6) ; DE = dst + ld hl, (iy + 3) ; HL = src +.L.loop: + ld a, (hl) ; A = src.lo + inc hl + ldi ; dst.lo = src.hi + ld (de), a ; dst.hi = A + inc de + jp pe, .L.loop + ret + +.else + +; void swab(const void *__restrict src, void *__restrict dst, ssize_t count) +_swab: + ; swab has unspecified behaviour when count is odd: + ; - this implementation will copy dst[count - 1] to src[count - 1] when count is odd + ; - does nothing when count <= 0 or count is negative + ld iy, 0 + add iy, sp + sra (iy + 11) + ret m ; do nothing when negative + ld bc, (iy + 9) + rr b + rr c + ; BC = count / 2 + sbc hl, hl + adc hl, bc + ; Carry is set when count is odd + ld de, (iy + 6) ; DE = dst + ld hl, (iy + 3) ; HL = src + jr z, .L.finish +.L.loop: + ld a, (hl) ; A = src.lo + inc hl + ldi ; dst.lo = src.hi + ld (de), a ; dst.hi = A + inc de + jp pe, .L.loop +.L.finish: + ret nc ; even count + ; copy one more byte + ld a, (hl) + ld (de), a + ret + +.endif diff --git a/src/libc/ungetc.c b/src/libc/ungetc.c new file mode 100644 index 000000000..e499e6129 --- /dev/null +++ b/src/libc/ungetc.c @@ -0,0 +1,9 @@ +#include "__fileioc_stdio.h" + +int __attribute__((weak)) ungetc(int ch, FILE *stream) +{ + // unimplemented + (void)ch; + (void)stream; + return EOF; +} diff --git a/test/standalone/fileno/autotest.json b/test/standalone/fileno/autotest.json new file mode 100644 index 000000000..9257ebf8f --- /dev/null +++ b/test/standalone/fileno/autotest.json @@ -0,0 +1,37 @@ +{ + "transfer_files": + [ + "bin/DEMO.8xp" + ], + "target": + { + "name": "DEMO", + "isASM": true + }, + "sequence": + [ + "action|launch", + "delay|500", + "hashWait|1", + "key|enter", + "delay|300", + "hashWait|2" + ], + "hashes": + { + "1": + { + "description": "All tests passed", + "start": "vram_start", + "size": "vram_16_size", + "expected_CRCs": [ "38E2AD5A" ] + }, + "2": + { + "description": "Test homescreen cleared", + "start": "vram_start", + "size": "vram_16_size", + "expected_CRCs": [ "FFAF89BA", "101734A5", "9DA19F44", "A32840C8", "349F4775" ] + } + } +} diff --git a/test/standalone/fileno/makefile b/test/standalone/fileno/makefile new file mode 100644 index 000000000..7f6394733 --- /dev/null +++ b/test/standalone/fileno/makefile @@ -0,0 +1,16 @@ +# ---------------------------- +# Makefile Options +# ---------------------------- + +NAME = DEMO +ICON = icon.png +DESCRIPTION = "CE C Toolchain Demo" +COMPRESSED = NO +ARCHIVED = NO + +CFLAGS = -Wall -Wextra -ffreestanding -Oz +CXXFLAGS = -Wall -Wextra -ffreestanding -Oz + +# ---------------------------- + +include $(shell cedev-config --makefile) diff --git a/test/standalone/fileno/src/main.cpp b/test/standalone/fileno/src/main.cpp new file mode 100644 index 000000000..9ec2c2331 --- /dev/null +++ b/test/standalone/fileno/src/main.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define C(expr) if (!(expr)) { return __LINE__; } + +static const char *name_1 = "foo1"; +static const char *name_2 = "bar2"; + +constexpr int FILEIOC_SLOT_COUNT = 5; +constexpr int MIN_FILEIOC_SLOT = 1 + std::max(STDIN_FILENO, std::max(STDOUT_FILENO, STDERR_FILENO)); +constexpr int MAX_FILEIOC_SLOT = MIN_FILEIOC_SLOT + (FILEIOC_SLOT_COUNT - 1); + +static bool is_fileioc_fd(int fd) { + if (fd == STDIN_FILENO || fd == STDOUT_FILENO || fd == STDERR_FILENO) { + return false; + } + return (fd >= MIN_FILEIOC_SLOT && fd <= MAX_FILEIOC_SLOT); +} + +int run_tests(void) { + + // create some files + FILE *file_1 = fopen(name_1, "w"); + C(file_1 != NULL); + FILE *file_2 = fopen(name_2, "a+"); + C(file_2 != NULL); + + C(errno == 0); + + // test fileno + + int fileno_1 = fileno(file_1); + C(is_fileioc_fd(fileno_1)); + int fileno_2 = fileno(file_2); + C(is_fileioc_fd(fileno_2)); + + C(fileno_1 != fileno_2); + C(errno == 0); + + int fileno_stdin = fileno(stdin); + int fileno_stdout = fileno(stdout); + int fileno_stderr = fileno(stderr); + + C(fileno_stdin == STDIN_FILENO); + C(fileno_stdout == STDOUT_FILENO); + + // we current define stdout to be stderr + if (stdout != stderr) { + C(fileno_stderr == STDERR_FILENO); + } else { + C(fileno_stderr == STDOUT_FILENO); + } + C(errno == 0); + + int fileno_nullptr = fileno(nullptr); + C(fileno_nullptr == -1); + C(errno == EBADF); + + errno = 0; + + // we currently treat stdin/stdout/stderr as a tty/terminal + C(isatty(fileno_stdin) == 1); + C(isatty(fileno_stdout) == 1); + C(isatty(fileno_stderr) == 1); + C(errno == 0); + + // test invalid fd + C(isatty(-1) == 0); + C(errno == EBADF); + errno = 0; + C(isatty(MAX_FILEIOC_SLOT + 1) == 0); + C(errno == EBADF); + errno = 0; + + // test valid fd that are not a terminal + C(isatty(fileno_1) == 0); + C(errno == ENOTTY); + errno = 0; + C(isatty(fileno_2) == 0); + C(errno == ENOTTY); + errno = 0; + + // remove the files + C(fclose(file_1) == 0); + file_1 = fopen(name_1, "r+"); + C(file_1 != NULL); + C(fclose(file_2) == 0); + C(remove(name_2) == 0); + C(fclose(file_1) == 0); + C(remove(name_1) == 0); + C(errno == 0); + + // attempt to read a removed file + file_1 = fopen(name_1, "r"); + C(file_1 == NULL); + + // discard errno value + errno = 0; + + return 0; +} + +extern "C" int main(void) { + os_ClrHome(); + errno = 0; + int failed_test = run_tests(); + if (errno != 0) { + perror("errno"); + } + if (failed_test != 0) { + char buf[sizeof("Failed test L-8388608")]; + boot_sprintf(buf, "Failed test L%d", failed_test); + puts(buf); + } else { + puts("All tests passed"); + } + + while (!os_GetCSC()); + + return 0; +} diff --git a/test/standalone/swab/autotest.json b/test/standalone/swab/autotest.json new file mode 100644 index 000000000..9257ebf8f --- /dev/null +++ b/test/standalone/swab/autotest.json @@ -0,0 +1,37 @@ +{ + "transfer_files": + [ + "bin/DEMO.8xp" + ], + "target": + { + "name": "DEMO", + "isASM": true + }, + "sequence": + [ + "action|launch", + "delay|500", + "hashWait|1", + "key|enter", + "delay|300", + "hashWait|2" + ], + "hashes": + { + "1": + { + "description": "All tests passed", + "start": "vram_start", + "size": "vram_16_size", + "expected_CRCs": [ "38E2AD5A" ] + }, + "2": + { + "description": "Test homescreen cleared", + "start": "vram_start", + "size": "vram_16_size", + "expected_CRCs": [ "FFAF89BA", "101734A5", "9DA19F44", "A32840C8", "349F4775" ] + } + } +} diff --git a/test/standalone/swab/makefile b/test/standalone/swab/makefile new file mode 100644 index 000000000..7f6394733 --- /dev/null +++ b/test/standalone/swab/makefile @@ -0,0 +1,16 @@ +# ---------------------------- +# Makefile Options +# ---------------------------- + +NAME = DEMO +ICON = icon.png +DESCRIPTION = "CE C Toolchain Demo" +COMPRESSED = NO +ARCHIVED = NO + +CFLAGS = -Wall -Wextra -ffreestanding -Oz +CXXFLAGS = -Wall -Wextra -ffreestanding -Oz + +# ---------------------------- + +include $(shell cedev-config --makefile) diff --git a/test/standalone/swab/src/main.c b/test/standalone/swab/src/main.c new file mode 100644 index 000000000..e5531d6d4 --- /dev/null +++ b/test/standalone/swab/src/main.c @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include + +int run_tests(void); + +int main(void) { + os_ClrHome(); + int failed_test = run_tests(); + if (failed_test != 0) { + char buf[sizeof("Failed test L-8388608")]; + boot_sprintf(buf, "Failed test L%d", failed_test); + puts(buf); + } else { + puts("All tests passed"); + } + + while (!os_GetCSC()); + + return 0; +} diff --git a/test/standalone/swab/src/run_tests.c b/test/standalone/swab/src/run_tests.c new file mode 100644 index 000000000..3fd36a9ac --- /dev/null +++ b/test/standalone/swab/src/run_tests.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include + +#define C(expr) if (!(expr)) { return __LINE__; } + +int run_tests(void) { + const unsigned char src[7] = { + 0, 1, 2, 3, 4, 5, 6 + }; + const unsigned char pattern[7] = { + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6 + }; + unsigned char dst[7]; + + // swab does nothing if count is 0 or negative + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[2], &dst[2], 0); + C(memcmp(dst, pattern, sizeof(dst)) == 0); + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[2], &dst[2], -1); + C(memcmp(dst, pattern, sizeof(dst)) == 0); + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[2], &dst[2], INT_MIN); + C(memcmp(dst, pattern, sizeof(dst)) == 0); + + // test non-zero amounts + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[2], &dst[2], 1); + C(dst[0] == 0xF0); + C(dst[1] == 0xF1); + // dst[2] is undefined + C(dst[3] == 0xF3); + C(dst[4] == 0xF4); + C(dst[5] == 0xF5); + C(dst[6] == 0xF6); + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[2], &dst[2], 2); + C(dst[0] == 0xF0); + C(dst[1] == 0xF1); + C(dst[2] == 3); + C(dst[3] == 2); + C(dst[4] == 0xF4); + C(dst[5] == 0xF5); + C(dst[6] == 0xF6); + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[2], &dst[2], 3); + C(dst[0] == 0xF0); + C(dst[1] == 0xF1); + C(dst[2] == 3); + C(dst[3] == 2); + // dst[4] is undefined + C(dst[5] == 0xF5); + C(dst[6] == 0xF6); + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[1], &dst[1], 4); + C(dst[0] == 0xF0); + C(dst[1] == 2); + C(dst[2] == 1); + C(dst[3] == 4); + C(dst[4] == 3); + C(dst[5] == 0xF5); + C(dst[6] == 0xF6); + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[1], &dst[1], 4); + C(dst[0] == 0xF0); + C(dst[1] == 2); + C(dst[2] == 1); + C(dst[3] == 4); + C(dst[4] == 3); + C(dst[5] == 0xF5); + C(dst[6] == 0xF6); + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[2], &dst[2], 4); + C(dst[0] == 0xF0); + C(dst[1] == 0xF1); + C(dst[2] == 3); + C(dst[3] == 2); + C(dst[4] == 5); + C(dst[5] == 4); + C(dst[6] == 0xF6); + + memcpy(dst, pattern, sizeof(dst)); + swab(&src[1], &dst[1], 5); + C(dst[0] == 0xF0); + C(dst[1] == 2); + C(dst[2] == 1); + C(dst[3] == 4); + C(dst[4] == 3); + // dst[5] is undefined + C(dst[6] == 0xF6); + + /* large test */ { + unsigned char * large_src = alloca(256); + unsigned char * large_dst = alloca(256); + + // setup src + for (int i = 0; i < 256; i++) { + large_src[i] = (unsigned char)i; + } + + // setup canary + large_dst[255] = 42; + swab(large_src, large_dst, 255); + + // large_dst[254] is undefined + for (int i = 0; i < 254; i++) { + if (i % 2 == 0) { + C(large_src[i] == large_dst[i + 1]); + } else { + C(large_src[i] == large_dst[i - 1]); + } + } + + // test that the canary was not overwritten + C(large_dst[255] == 42); + swab(large_src, large_dst, 256); + for (int i = 0; i < 256; i++) { + if (i % 2 == 0) { + C(large_src[i] == large_dst[i + 1]); + } else { + C(large_src[i] == large_dst[i - 1]); + } + } + + // swab is an involute function + swab(large_dst, large_src, 256); + for (int i = 0; i < 256; i++) { + C(large_src[i] == (unsigned char)i); + } + } + + return 0; +}