From 4a081501d809fa1c1ea06fed104028509fb392fa Mon Sep 17 00:00:00 2001 From: Alexander Petrov Date: Mon, 6 Jul 2026 16:08:58 +0300 Subject: [PATCH] =?UTF-8?q?libc:=20=D1=81=D0=BF=D0=BB=D0=B8=D1=82=20=C2=AB?= =?UTF-8?q?1=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D1=8F=20=3D=201=20?= =?UTF-8?q?=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C=C2=BB=20=E2=80=94=20=D0=B2?= =?UTF-8?q?=D1=81=D1=8F=20=D0=B1=D0=B8=D0=B1=D0=BB=D0=B8=D0=BE=D1=82=D0=B5?= =?UTF-8?q?=D0=BA=D0=B0,=20wildcard-=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bios/conio/env/errno/gfx/io/mem/mouse/stdio/stdlib/string/sys/time/ video разложены по модулям: общие статики и helpers — в internal _-модулях (_conio.h/_mouse.h/_gfx.h/_palette.h/_atexit.h/_time.h) - lib/Makefile: LIBC_C = wildcard libc/*/*.c — гранулярность файлов = гранулярность DCE линкера - эффект _CODE: gfx_text 6986→2568 Б, gfx_mous −1745, gfx_demo/d16 −542; ранее timedir −3270, ls −3098, stattest −2995 - комментарии оставшихся модулей переведены на русский; puts: убран мёртвый pchars; videomode_raw разложен на get/set - docs/libc-split-asm-cases.md — правила asm-связок между модулями; docs/libc-roadmap.md — план этапа - восстановлен examples/mdview/SAMPLE.MD (нужен make floppy) Co-Authored-By: Claude Fable 5 --- docs/libc-roadmap.md | 131 ++++ docs/libc-split-asm-cases.md | 52 ++ examples/mdview/SAMPLE.MD | 5 + lib/Makefile | 50 +- libc/bios/bios_clearwin.c | 33 + libc/bios/bios_clearwin_ch.c | 35 + libc/bios/bios_fillattr.c | 20 + libc/bios/bios_fillchar.c | 19 + libc/bios/bios_fillcharattr.c | 26 + libc/bios/bios_get_place.c | 16 + libc/bios/bios_scrollwin.c | 27 + libc/bios/bios_set_place.c | 20 + libc/bios/bios_write.c | 25 + libc/bios/bios_write_stop.c | 27 + libc/bios/bios_write_until.c | 27 + libc/bios/bios_writeattr.c | 29 + libc/bios/bios_writeattr_stop.c | 30 + libc/bios/bios_writeattr_until.c | 29 + libc/bios/text.c | 302 -------- libc/conio/_bios_putchar.c | 21 + libc/conio/_conio.h | 49 ++ libc/conio/_g_text_attr.c | 12 + libc/conio/_get_cursor.c | 16 + libc/conio/_pc_place.c | 10 + libc/conio/_pc_raw_mode.c | 13 + libc/conio/_putch_wrchar.c | 98 +++ libc/conio/_putch_wrchar_raw.c | 37 + libc/conio/_set_cursor.c | 16 + libc/conio/cgets.c | 33 + libc/conio/clrscr.c | 15 + libc/conio/clrscr_attr.c | 24 + libc/conio/conio.c | 678 ------------------ libc/conio/cprintf.c | 13 +- libc/conio/cputs.c | 72 ++ libc/conio/get_putch_raw_mode.c | 11 + libc/conio/get_text_attr.c | 10 + libc/conio/getch.c | 18 + libc/conio/getche.c | 18 + libc/conio/getkey.c | 26 + libc/conio/gettextmode.c | 15 + libc/conio/gotoxy.c | 20 + libc/conio/kbhit.c | 23 + libc/conio/putch.c | 41 ++ libc/conio/rdchar.c | 23 + libc/conio/scroll.c | 30 + libc/conio/set_putch_raw_mode.c | 12 + libc/conio/set_text_attr.c | 14 + libc/conio/settextmode.c | 22 + libc/conio/text_pal_get.c | 12 + libc/conio/text_pal_get_color.c | 13 + libc/conio/text_pal_load.c | 14 + libc/conio/text_pal_reset.c | 12 + libc/conio/text_pal_set_color.c | 13 + libc/conio/text_palette.c | 42 -- libc/conio/textattr.c | 11 + libc/conio/textbackground.c | 13 + libc/conio/textcolor.c | 13 + libc/conio/wherex.c | 17 + libc/conio/wherexy.c | 17 + libc/conio/wherey.c | 17 + libc/conio/wrchar.c | 32 + libc/conio/z80_inp.c | 17 + libc/conio/z80_outp.c | 17 + libc/env/env.c | 93 --- libc/env/getenv.c | 41 ++ libc/env/putenv.c | 26 + libc/env/sysenv.c | 28 + libc/errno/_errno_set.c | 44 +- libc/errno/perror.c | 18 + libc/errno/{errno.c => strerror.c} | 34 +- libc/gfx/_gfx.h | 109 +++ libc/gfx/_gfx_acc256.c | 12 + libc/gfx/_gfx_clear16_raw.c | 20 + libc/gfx/_gfx_clear256_raw.c | 21 + libc/gfx/_gfx_font_ensure.c | 12 + libc/gfx/_gfx_font_state.c | 16 + libc/gfx/_gfx_g16_state.c | 14 + libc/gfx/_gfx_hfill16.c | 38 + libc/gfx/_gfx_hfill256.c | 45 ++ libc/gfx/_gfx_hline16_raw.c | 50 ++ libc/gfx/_gfx_hline256_raw.c | 28 + libc/gfx/_gfx_putpixel16_raw.c | 22 + libc/gfx/_gfx_putpixel256_raw.c | 30 + libc/gfx/_gfx_render_row16.c | 53 ++ libc/gfx/_gfx_rmw16.c | 28 + libc/gfx/_gfx_state.c | 20 + libc/gfx/_gfx_t16_pair_build.c | 17 + libc/gfx/_gfx_t16_state.c | 14 + libc/gfx/_gfx_vline16_raw.c | 30 + libc/gfx/_gfx_vline256_raw.c | 50 ++ libc/gfx/_gfx_w3_state.c | 10 + libc/gfx/_gfx_w3_video_begin.c | 20 + libc/gfx/_gfx_w3_video_end.c | 15 + libc/gfx/gfx_16.c | 106 --- libc/gfx/gfx_256.c | 118 --- libc/gfx/gfx_clear16.c | 12 + libc/gfx/gfx_clear256.c | 12 + libc/gfx/gfx_core.c | 141 ---- libc/gfx/gfx_done.c | 10 + libc/gfx/gfx_fill_rect16.c | 16 + libc/gfx/gfx_fill_rect256.c | 28 + libc/gfx/gfx_font.c | 63 -- libc/gfx/gfx_get_bank.c | 10 + libc/gfx/gfx_get_draw_page.c | 10 + libc/gfx/gfx_get_visible_page.c | 10 + libc/gfx/gfx_hline16.c | 12 + libc/gfx/gfx_hline256.c | 12 + libc/gfx/gfx_init.c | 19 + libc/gfx/gfx_line16.c | 35 + libc/gfx/gfx_line256.c | 37 + libc/gfx/gfx_load_default_font.c | 28 + libc/gfx/gfx_pal_get.c | 13 + libc/gfx/gfx_pal_get_color.c | 13 + libc/gfx/gfx_pal_load.c | 40 ++ libc/gfx/gfx_pal_reset.c | 12 + libc/gfx/gfx_pal_set.c | 17 + libc/gfx/gfx_palette.c | 74 -- libc/gfx/gfx_putchar16.c | 27 + libc/gfx/{gfx_text_256.c => gfx_putchar256.c} | 40 +- libc/gfx/gfx_putpixel16.c | 14 + libc/gfx/gfx_putpixel256.c | 14 + libc/gfx/gfx_raw_16.c | 186 ----- libc/gfx/gfx_raw_256.c | 175 ----- libc/gfx/gfx_raw_common.c | 44 -- libc/gfx/gfx_rect16.c | 19 + libc/gfx/gfx_rect256.c | 19 + libc/gfx/gfx_set_bank.c | 10 + libc/gfx/gfx_set_draw_page.c | 14 + libc/gfx/gfx_set_font.c | 12 + libc/gfx/gfx_set_visible_page.c | 22 + libc/gfx/gfx_text16.c | 29 + libc/gfx/gfx_text256.c | 15 + libc/gfx/gfx_text_16.c | 124 ---- libc/gfx/gfx_vline16.c | 12 + libc/gfx/gfx_vline256.c | 12 + libc/gfx/gfx_wait_vsync.c | 18 + libc/include/stdlib.h | 5 + libc/io/chdir.c | 24 + libc/io/creat.c | 14 + libc/io/dir.c | 68 -- libc/io/ffirst.c | 43 ++ libc/io/fnext.c | 27 + libc/io/fsdir.c | 88 --- libc/io/fstat.c | 91 +++ libc/io/getcwd.c | 28 + libc/io/lseek.c | 49 +- libc/io/mkdir.c | 24 + libc/io/read.c | 48 +- libc/io/rmdir.c | 24 + libc/io/stat.c | 110 +-- libc/io/unlink.c | 6 +- libc/mem/bank_io_w1.c | 52 -- libc/mem/bank_io_w3.c | 52 -- libc/mem/bank_load_byte.c | 23 + libc/mem/bank_load_byte_w1.c | 22 + libc/mem/bank_read.c | 18 + libc/mem/bank_read_w1.c | 18 + libc/mem/bank_store_byte.c | 17 + libc/mem/bank_store_byte_w1.c | 17 + libc/mem/bank_write.c | 18 + libc/mem/bank_write_w1.c | 18 + libc/mem/mem_alloc_pages_bios.c | 34 + libc/mem/mem_alloc_pages_estex.c | 31 + libc/mem/mem_bios.c | 138 ---- libc/mem/mem_estex.c | 121 ---- libc/mem/mem_free_block_bios.c | 24 + libc/mem/mem_free_block_estex.c | 22 + libc/mem/mem_get_page_bios.c | 31 + libc/mem/mem_info_bios.c | 45 ++ libc/mem/mem_info_estex.c | 44 ++ libc/mouse/_mouse.h | 21 + libc/mouse/_mouse_scratch.c | 13 + libc/mouse/_ms_query.c | 20 + libc/mouse/mouse.c | 305 -------- libc/mouse/mouse_bounds_x.c | 19 + libc/mouse/mouse_bounds_y.c | 19 + libc/mouse/mouse_get_cursor.c | 42 ++ libc/mouse/mouse_get_sensitivity_x.c | 11 + libc/mouse/mouse_get_sensitivity_y.c | 11 + libc/mouse/mouse_goto.c | 18 + libc/mouse/mouse_hide.c | 16 + libc/mouse/mouse_init.c | 23 + libc/mouse/mouse_load_cursor.c | 35 + libc/mouse/mouse_read.c | 46 ++ libc/mouse/mouse_refresh.c | 17 + libc/mouse/mouse_set_sensitivity.c | 20 + libc/mouse/mouse_show.c | 16 + libc/mouse/mouse_text_cursor.c | 39 + libc/mouse/mouse_video_mode_changed.c | 20 + libc/stdio/dec_print.c | 97 +-- libc/stdio/getchar.c | 11 +- libc/stdio/hex16.c | 22 + libc/stdio/hex32.c | 19 + libc/stdio/hex8.c | 35 + libc/stdio/hex_print.c | 68 -- libc/stdio/putchar.c | 18 +- libc/stdio/puts.c | 38 +- libc/stdlib/max.c | 32 + libc/stdlib/min.c | 29 + libc/stdlib/minmax.c | 42 -- libc/string/strlwr.c | 9 +- libc/string/strupr.c | 9 +- libc/sys/_atexit.h | 13 + libc/sys/_atexit_state.c | 10 + libc/sys/_exit.c | 21 + libc/sys/atexit.c | 54 +- libc/sys/exit.c | 19 + libc/time/_time.h | 23 + libc/time/_tm_is_leap.c | 8 + libc/time/_tm_mdays.c | 8 + libc/time/_tm_month_days.c | 12 + libc/time/_tm_year_days.c | 11 + libc/time/asctime.c | 27 + libc/time/ctime.c | 8 + libc/time/getdatetime.c | 49 ++ libc/time/gmtime.c | 44 ++ libc/time/localtime.c | 8 + libc/time/mktime.c | 22 + libc/time/posix_time.c | 143 ---- libc/time/setdatetime.c | 50 ++ libc/time/sleep.c | 18 +- libc/time/time.c | 105 +-- libc/video/_pal_args.c | 10 + libc/video/_palette.h | 23 + libc/video/_videomode_raw_get.c | 20 + libc/video/_videomode_raw_set.c | 30 + libc/video/pal_get.c | 28 + libc/video/pal_get_color.c | 16 + libc/video/pal_load.c | 30 + libc/video/pal_reset.c | 14 + libc/video/pal_reset_at.c | 30 + libc/video/pal_set_color.c | 17 + libc/video/palette.c | 131 ---- libc/video/videomode_raw.c | 46 -- tests/conio/conio.c | 4 +- tests/hello/hello.c | 6 +- tests/text_palette/text_palette.c | 6 +- 237 files changed, 4830 insertions(+), 3933 deletions(-) create mode 100644 docs/libc-roadmap.md create mode 100644 docs/libc-split-asm-cases.md create mode 100644 examples/mdview/SAMPLE.MD create mode 100644 libc/bios/bios_clearwin.c create mode 100644 libc/bios/bios_clearwin_ch.c create mode 100644 libc/bios/bios_fillattr.c create mode 100644 libc/bios/bios_fillchar.c create mode 100644 libc/bios/bios_fillcharattr.c create mode 100644 libc/bios/bios_get_place.c create mode 100644 libc/bios/bios_scrollwin.c create mode 100644 libc/bios/bios_set_place.c create mode 100644 libc/bios/bios_write.c create mode 100644 libc/bios/bios_write_stop.c create mode 100644 libc/bios/bios_write_until.c create mode 100644 libc/bios/bios_writeattr.c create mode 100644 libc/bios/bios_writeattr_stop.c create mode 100644 libc/bios/bios_writeattr_until.c delete mode 100644 libc/bios/text.c create mode 100644 libc/conio/_bios_putchar.c create mode 100644 libc/conio/_conio.h create mode 100644 libc/conio/_g_text_attr.c create mode 100644 libc/conio/_get_cursor.c create mode 100644 libc/conio/_pc_place.c create mode 100644 libc/conio/_pc_raw_mode.c create mode 100644 libc/conio/_putch_wrchar.c create mode 100644 libc/conio/_putch_wrchar_raw.c create mode 100644 libc/conio/_set_cursor.c create mode 100644 libc/conio/cgets.c create mode 100644 libc/conio/clrscr.c create mode 100644 libc/conio/clrscr_attr.c delete mode 100644 libc/conio/conio.c create mode 100644 libc/conio/cputs.c create mode 100644 libc/conio/get_putch_raw_mode.c create mode 100644 libc/conio/get_text_attr.c create mode 100644 libc/conio/getch.c create mode 100644 libc/conio/getche.c create mode 100644 libc/conio/getkey.c create mode 100644 libc/conio/gettextmode.c create mode 100644 libc/conio/gotoxy.c create mode 100644 libc/conio/kbhit.c create mode 100644 libc/conio/putch.c create mode 100644 libc/conio/rdchar.c create mode 100644 libc/conio/scroll.c create mode 100644 libc/conio/set_putch_raw_mode.c create mode 100644 libc/conio/set_text_attr.c create mode 100644 libc/conio/settextmode.c create mode 100644 libc/conio/text_pal_get.c create mode 100644 libc/conio/text_pal_get_color.c create mode 100644 libc/conio/text_pal_load.c create mode 100644 libc/conio/text_pal_reset.c create mode 100644 libc/conio/text_pal_set_color.c delete mode 100644 libc/conio/text_palette.c create mode 100644 libc/conio/textattr.c create mode 100644 libc/conio/textbackground.c create mode 100644 libc/conio/textcolor.c create mode 100644 libc/conio/wherex.c create mode 100644 libc/conio/wherexy.c create mode 100644 libc/conio/wherey.c create mode 100644 libc/conio/wrchar.c create mode 100644 libc/conio/z80_inp.c create mode 100644 libc/conio/z80_outp.c delete mode 100644 libc/env/env.c create mode 100644 libc/env/getenv.c create mode 100644 libc/env/putenv.c create mode 100644 libc/env/sysenv.c create mode 100644 libc/errno/perror.c rename libc/errno/{errno.c => strerror.c} (58%) create mode 100644 libc/gfx/_gfx.h create mode 100644 libc/gfx/_gfx_acc256.c create mode 100644 libc/gfx/_gfx_clear16_raw.c create mode 100644 libc/gfx/_gfx_clear256_raw.c create mode 100644 libc/gfx/_gfx_font_ensure.c create mode 100644 libc/gfx/_gfx_font_state.c create mode 100644 libc/gfx/_gfx_g16_state.c create mode 100644 libc/gfx/_gfx_hfill16.c create mode 100644 libc/gfx/_gfx_hfill256.c create mode 100644 libc/gfx/_gfx_hline16_raw.c create mode 100644 libc/gfx/_gfx_hline256_raw.c create mode 100644 libc/gfx/_gfx_putpixel16_raw.c create mode 100644 libc/gfx/_gfx_putpixel256_raw.c create mode 100644 libc/gfx/_gfx_render_row16.c create mode 100644 libc/gfx/_gfx_rmw16.c create mode 100644 libc/gfx/_gfx_state.c create mode 100644 libc/gfx/_gfx_t16_pair_build.c create mode 100644 libc/gfx/_gfx_t16_state.c create mode 100644 libc/gfx/_gfx_vline16_raw.c create mode 100644 libc/gfx/_gfx_vline256_raw.c create mode 100644 libc/gfx/_gfx_w3_state.c create mode 100644 libc/gfx/_gfx_w3_video_begin.c create mode 100644 libc/gfx/_gfx_w3_video_end.c delete mode 100644 libc/gfx/gfx_16.c delete mode 100644 libc/gfx/gfx_256.c create mode 100644 libc/gfx/gfx_clear16.c create mode 100644 libc/gfx/gfx_clear256.c delete mode 100644 libc/gfx/gfx_core.c create mode 100644 libc/gfx/gfx_done.c create mode 100644 libc/gfx/gfx_fill_rect16.c create mode 100644 libc/gfx/gfx_fill_rect256.c delete mode 100644 libc/gfx/gfx_font.c create mode 100644 libc/gfx/gfx_get_bank.c create mode 100644 libc/gfx/gfx_get_draw_page.c create mode 100644 libc/gfx/gfx_get_visible_page.c create mode 100644 libc/gfx/gfx_hline16.c create mode 100644 libc/gfx/gfx_hline256.c create mode 100644 libc/gfx/gfx_init.c create mode 100644 libc/gfx/gfx_line16.c create mode 100644 libc/gfx/gfx_line256.c create mode 100644 libc/gfx/gfx_load_default_font.c create mode 100644 libc/gfx/gfx_pal_get.c create mode 100644 libc/gfx/gfx_pal_get_color.c create mode 100644 libc/gfx/gfx_pal_load.c create mode 100644 libc/gfx/gfx_pal_reset.c create mode 100644 libc/gfx/gfx_pal_set.c delete mode 100644 libc/gfx/gfx_palette.c create mode 100644 libc/gfx/gfx_putchar16.c rename libc/gfx/{gfx_text_256.c => gfx_putchar256.c} (51%) create mode 100644 libc/gfx/gfx_putpixel16.c create mode 100644 libc/gfx/gfx_putpixel256.c delete mode 100644 libc/gfx/gfx_raw_16.c delete mode 100644 libc/gfx/gfx_raw_256.c delete mode 100644 libc/gfx/gfx_raw_common.c create mode 100644 libc/gfx/gfx_rect16.c create mode 100644 libc/gfx/gfx_rect256.c create mode 100644 libc/gfx/gfx_set_bank.c create mode 100644 libc/gfx/gfx_set_draw_page.c create mode 100644 libc/gfx/gfx_set_font.c create mode 100644 libc/gfx/gfx_set_visible_page.c create mode 100644 libc/gfx/gfx_text16.c create mode 100644 libc/gfx/gfx_text256.c delete mode 100644 libc/gfx/gfx_text_16.c create mode 100644 libc/gfx/gfx_vline16.c create mode 100644 libc/gfx/gfx_vline256.c create mode 100644 libc/gfx/gfx_wait_vsync.c create mode 100644 libc/io/chdir.c create mode 100644 libc/io/creat.c delete mode 100644 libc/io/dir.c create mode 100644 libc/io/ffirst.c create mode 100644 libc/io/fnext.c delete mode 100644 libc/io/fsdir.c create mode 100644 libc/io/fstat.c create mode 100644 libc/io/getcwd.c create mode 100644 libc/io/mkdir.c create mode 100644 libc/io/rmdir.c delete mode 100644 libc/mem/bank_io_w1.c delete mode 100644 libc/mem/bank_io_w3.c create mode 100644 libc/mem/bank_load_byte.c create mode 100644 libc/mem/bank_load_byte_w1.c create mode 100644 libc/mem/bank_read.c create mode 100644 libc/mem/bank_read_w1.c create mode 100644 libc/mem/bank_store_byte.c create mode 100644 libc/mem/bank_store_byte_w1.c create mode 100644 libc/mem/bank_write.c create mode 100644 libc/mem/bank_write_w1.c create mode 100644 libc/mem/mem_alloc_pages_bios.c create mode 100644 libc/mem/mem_alloc_pages_estex.c delete mode 100644 libc/mem/mem_bios.c delete mode 100644 libc/mem/mem_estex.c create mode 100644 libc/mem/mem_free_block_bios.c create mode 100644 libc/mem/mem_free_block_estex.c create mode 100644 libc/mem/mem_get_page_bios.c create mode 100644 libc/mem/mem_info_bios.c create mode 100644 libc/mem/mem_info_estex.c create mode 100644 libc/mouse/_mouse.h create mode 100644 libc/mouse/_mouse_scratch.c create mode 100644 libc/mouse/_ms_query.c delete mode 100644 libc/mouse/mouse.c create mode 100644 libc/mouse/mouse_bounds_x.c create mode 100644 libc/mouse/mouse_bounds_y.c create mode 100644 libc/mouse/mouse_get_cursor.c create mode 100644 libc/mouse/mouse_get_sensitivity_x.c create mode 100644 libc/mouse/mouse_get_sensitivity_y.c create mode 100644 libc/mouse/mouse_goto.c create mode 100644 libc/mouse/mouse_hide.c create mode 100644 libc/mouse/mouse_init.c create mode 100644 libc/mouse/mouse_load_cursor.c create mode 100644 libc/mouse/mouse_read.c create mode 100644 libc/mouse/mouse_refresh.c create mode 100644 libc/mouse/mouse_set_sensitivity.c create mode 100644 libc/mouse/mouse_show.c create mode 100644 libc/mouse/mouse_text_cursor.c create mode 100644 libc/mouse/mouse_video_mode_changed.c create mode 100644 libc/stdio/hex16.c create mode 100644 libc/stdio/hex32.c create mode 100644 libc/stdio/hex8.c delete mode 100644 libc/stdio/hex_print.c create mode 100644 libc/stdlib/max.c create mode 100644 libc/stdlib/min.c delete mode 100644 libc/stdlib/minmax.c create mode 100644 libc/sys/_atexit.h create mode 100644 libc/sys/_atexit_state.c create mode 100644 libc/sys/_exit.c create mode 100644 libc/sys/exit.c create mode 100644 libc/time/_time.h create mode 100644 libc/time/_tm_is_leap.c create mode 100644 libc/time/_tm_mdays.c create mode 100644 libc/time/_tm_month_days.c create mode 100644 libc/time/_tm_year_days.c create mode 100644 libc/time/asctime.c create mode 100644 libc/time/ctime.c create mode 100644 libc/time/getdatetime.c create mode 100644 libc/time/gmtime.c create mode 100644 libc/time/localtime.c create mode 100644 libc/time/mktime.c delete mode 100644 libc/time/posix_time.c create mode 100644 libc/time/setdatetime.c create mode 100644 libc/video/_pal_args.c create mode 100644 libc/video/_palette.h create mode 100644 libc/video/_videomode_raw_get.c create mode 100644 libc/video/_videomode_raw_set.c create mode 100644 libc/video/pal_get.c create mode 100644 libc/video/pal_get_color.c create mode 100644 libc/video/pal_load.c create mode 100644 libc/video/pal_reset.c create mode 100644 libc/video/pal_reset_at.c create mode 100644 libc/video/pal_set_color.c delete mode 100644 libc/video/palette.c delete mode 100644 libc/video/videomode_raw.c diff --git a/docs/libc-roadmap.md b/docs/libc-roadmap.md new file mode 100644 index 0000000..e6f3291 --- /dev/null +++ b/docs/libc-roadmap.md @@ -0,0 +1,131 @@ +# libc — план работ (на рассмотрение, 2026-07-06) + +Анализ после сплита четвёрки лидеров (time/stat/file/conio, итог −19.6 КБ +суммарно по _CODE приложений). Ниже — что ещё стоит сделать, по приоритетам. + +--- + +## П1. Досплит остальной libc — СДЕЛАНО 2026-07-06 + +Всё из таблицы ниже посплитано (кроме dec_print — осознанно оставлен). +gfx: 40 модулей (внутренний заголовок `libc/gfx/_gfx.h`, скретчи в +data-модулях `_gfx_state/_gfx_w3_state/_gfx_acc256/_gfx_g16_state/ +_gfx_font_state`, helpers `_gfx_hfill256/_gfx_hfill16/_gfx_rmw16/ +_gfx_text16`); video/palette → 7 модулей + `_palette.h`; +conio/text_palette → 5 модулей. Эффект (_CODE, Б): +gfx_demo 3769→3227, gfx_d16 3869→3327, gfx_text 6986→2568, +gfx_mous 7287→5542. Не-gfx тесты не изменились. + +Правило было: 1 публичная функция = 1 модуль, state/helpers — в отдельные +internal-модули, комментарии на русском, без `= 0`. + +| Файл | Ф-ий | _CODE | Замечания | +|---|---|---|---| +| gfx/gfx_256.c + gfx_16.c | 13+13 | 1277+1159 Б | самый жирный кусок; резать по примитивам (putpixel/line/hline/vline/rect/fill/clear/text). Учесть SMC-паттерны акселератора и кэш Port_Y — state в data-модули | +| gfx/gfx_raw_16 / raw_256 / raw_common / core / palette / font / text_* | ~40 | ~2.7 КБ | вместе с предыдущим — весь gfx | +| mouse/mouse.c | 16 | 308 Б | state mb_*/mc_* → data-модули | +| bios/text.c | 14 | 264 Б | все `__naked`, сплит чистый | +| io/open.c | 3+3 | 216 Б | open/creat/close; asm-хелперы `_estex_*_raw` у единственных потребителей | +| errno/errno.c | 2 | 680 Б | strerror + таблица строк неразделимы (один модуль); perror — отдельно, зовёт strerror | +| video/palette.c | 6 | 348 Б | | +| conio/text_palette.c | 5 | 140 Б | | +| io/read.c | 2 | 60 Б | read + write — обязательно врозь (write-only приложения) | +| io/fsdir.c | 4 | 80 Б | mkdir/rmdir/chdir/getcwd | +| env/env.c | 3 | 78 Б | getenv/putenv/sysenv + общий env_buf → data-модуль | +| mem/mem_bios, mem_estex, bank_io_w1/w3 | 3–4 каждый | ~430 Б | | +| sys/atexit.c | 3 | 106 Б | atexit/exit/_exit; общий стек хендлеров → data-модуль (exit тянется всегда из crt0, выигрыш небольшой но правильный) | +| io/dir.c | 2 | 53 Б | ffirst/fnext | +| stdlib/minmax.c | 2 | 27 Б | min/max врозь | +| stdio/hex_print.c | 3 | 38 Б | сплит чистый (call/jp по именам) — см. docs/libc-split-asm-cases.md | +| stdio/dec_print.c | 3 | 176 Б | НЕ резать (общее тело); открытое решение: вариант «3 независимых цикла» — п. отложен | + +Ожидаемый эффект: графические приложения −1–2 КБ, mouse/BIOS-text — сотни байт. + +## П2. FILE* — отложенные баги и недостающее + +Из шапки бывшего file.c («PROVISIONAL», stdio-review issues 3/4/5): + +- [x] fwrite: короткая запись ставит _F_ERROR (issue 3) — сделано 2026-07-06 +- [x] fgets(n=1): возвращает пустую строку по стандарту (issue 4) — сделано 2026-07-06 +- [x] mode_to_flags: проверено — парсер сканирует весь хвост режима, «rb+» работает (issue 5, уже был исправлен) +- [x] **fprintf/vfprintf** — сделаны 2026-07-06 (vsprintf в статический 256-байтовый буфер + fwrite) +- [x] ungetc — 1-байтный putback через поле hold; работает и на stdin — сделано 2026-07-06 +- [x] Буферизация FILE v2 — **реализован вариант B+** (2026-07-06): единый ленивый буфер BUFSIZ=512 на чтение и запись с автопереключением направления, статическая таблица OPEN_MAX=8 слотов, _fclosall через atexit, fflush(NULL) = все потоки. Дизайн: docs/file-buffering-design.md. Цена: filetest (использует всё) 7411→9929 Б _CODE; не-FILE программы не платят ничего. **Ждёт MAME-прогона: filetest, fdmax (лимит DSS), fbench (замер скорости)** +- [ ] fdopen/freopen/fclosall/fgetpos/fsetpos — по мере надобности (Solid-C категория C) + +## П3. Solid-C совместимость — остатки (docs/solid_c_compatibility.md) + +Phase 1 (алиасы) фактически закрыт — home/inp/outp/enable/disable, ms_*, +getc/putc, min/max, dec*/hex*, strlwr/strupr, cgets есть. Осталось: + +- [ ] getdisk()/setdisk() — ESTEX $02 CURDISK / $01 CHDISK +- [ ] getdate/gettime/setdate/settime — обёртки над getdatetime/setdatetime +- [ ] seek/tell/ltell, setmem/movmem, isascii, abort(), div(), remove→unlink, _ffirst +- [ ] errno-константы Solid-C (EZERO/EINVFNC/…) как алиасы в errno.h +- [ ] `` — зонтичный compat-заголовок +- [ ] обновить статусы/history в solid_c_compatibility.md (Phase 1 done) + +Research (Phase 3): absread/abswrite (найти ESTEX READ_SECT/WRITE_SECT), +scanf-семейство (**в SDCC 4.5 z80 его НЕТ** — писать самим или задокументировать +отказ), brk/sbrk vs SDCC heap, isatty, ioctl (скорее скип). + +## П4. Недостающие POSIX-мелочи вне Solid-C списка + +- [ ] **rename()** — в gap-анализе отсутствует, ESTEX почти наверняка имеет + RENAME (~$0F, рядом с DELETE $0E) — проверить по докам ESTEX и добавить +- [ ] isatty(fd) — тривиально (fd<0/флаги) + +## П5. Заголовки и гигиена сборки + +- [ ] Аудит затенения SDCC-заголовков: stdlib.h уже на `#include_next`; + stdio.h/time.h — полные замены (задокументировать контракт «что обязаны + объявлять»); подумать про собственный string.h (include_next + strlwr/ + strupr из sprinter_compat.h) +- [ ] lib/build: wildcard не удаляет stale .rel при удалении исходника — + либо чистка в Makefile (сверка списка), либо привычка `make clean` + после переименований (это уже укусило: старый file.rel лежал в build) +- [ ] Включить extra-тесты (conio2, dec_test, gets, hello2, simple, stest2, + winrest, bios_text, text_palette, gfx_dbuf, mdview2) в top-level + TESTS/APPS — иначе `make all` их не собирает и их baseline протухает + (уже дало ложные «+17 Б» при сравнении) +- [ ] Размерный регресс-тест: скрипт сравнения _CODE по .map с эталоном + (ловить случайные разжирения при правках libc) + +## П6. Верификация после сплита + +- [ ] `make floppy` + прогон в MAME ключевых тестов (conio, filetest, ptime, + stattest, mouse, gfx_demo) — линковка прошла, но поведение надо + подтвердить на эмуляторе +- [ ] Потом на железе (mdview2 и так ждёт проверки на железе — совместить) + +## П7. Документация + +- [ ] docs/libc-reference.md — сводный справочник API (по заголовкам: + сигнатура + 1 строка описания + особенности ABI); сейчас знание + размазано по memory/ и комментариям +- [ ] TODO.md: этапы 5/6/8 закрыты — перенести в history, добавить «этап 9 — + libc-оптимизация» (этот план) +- [ ] В CLAUDE.md/README зафиксировать правила libc: 1 ф-я = 1 модуль, + internal `_`-модули, русские комментарии, без `= 0`, asm-правила + (docs/libc-split-asm-cases.md) + +## П8. Смежное (не libc, из TODO.md — чтобы не потерялось) + +- auto-banking Phase 1 (toolchain/auto_bank.py) — когда проект перерастёт ~30 КБ +- IM2 ISR v2 (docs/im2_isr_design.md) — отложено решением 2026-06-01 +- font-quad для 640×256 (per-cell палитра) +- factoring parse_argv из crt0/crt0_banked в общий argv.s +- check_banks.py: разбивка code/const/bss per bank (косметика) + +--- + +## Предлагаемый порядок + +1. **П1-лайт**: io/env/errno/atexit/minmax/mem/dir/fsdir (мелкие, час работы, + выигрыш для всех CLI-приложений) + mouse + bios/text. +2. **П6**: MAME-смоук — подтвердить, что сплит ничего не сломал в рантайме, + до того как менять что-то ещё. +3. **П1-gfx**: разбор графики (самый большой кусок, отдельный заход). +4. **П2**: баги FILE* (3 шт.) + fprintf + ungetc. +5. **П3/П4**: solid-c остатки + rename/isatty. +6. **П5/П7**: гигиена сборки и документация — фоном, по кусочку. diff --git a/docs/libc-split-asm-cases.md b/docs/libc-split-asm-cases.md new file mode 100644 index 0000000..11bd4d7 --- /dev/null +++ b/docs/libc-split-asm-cases.md @@ -0,0 +1,52 @@ +# libc split: asm-связки между функциями + +Журнал случаев, найденных при разбиении libc на «1 публичная функция = 1 модуль» +(2026-07-05). Сюда записывается каждый обнаруженный переход `jr _func` или +`jr/jp/call` на метку **внутри другой функции** — такие связки нельзя разрывать +механически, разбираем каждую отдельно. + +## Правила (справка) + +| Паттерн | Через границу модулей | +|---|---| +| `call/jp _func` (публичная C-функция) | работает — метка глобальная (`::`) | +| `call/jp _label` на метку в чужой функции | работает, только если метка объявлена `_label::` | +| `jr` / `djnz` в другой модуль | **запрещено** — ±128 байт, разложение модулей не гарантировано | +| fall-through (без перехода, в надежде на соседство) | **не работает никогда** | + +## Случаи + +### 1. stdio/dec_print.c — dec8/dec16/dec32: разделяемое тело (НЕ разрывать) + +Статус: **оставлены в одном файле, решение отдельно.** + +- `dec8` → `jp __dec_entry3` — прыжок в середину тела `dec32`; +- `dec16` → `jp __dec_entry5` — то же; +- метки уже глобальные (`__dec_entry3::`, `__dec_entry5::`) — линковаться будет, + но выигрыша от сплита нет: dec8 всё равно притянет модуль с телом dec32; +- внутри хвоста: `_dec_get_d16` **fall-through** в `_dec_emit_or_skip`, + `_dec_get_d32` → `jr _dec_emit_or_skip`, общий флаг `_dec_flag`. + +Это осознанный дизайн из solid-c: тройка делит per-digit код. Варианты на потом: +(а) оставить как есть (176 Б тянутся целиком — терпимо); +(б) развести на 3 независимых цикла — dec8 станет ~40 Б, но исходник длиннее +и суммарно в exe, использующем dec8+dec32, станет хуже. Решение отложено. + +### 2. stdio/hex_print.c — чист + +`hex16` → `call _hex8` / `jp _hex8` (tail), `hex32` → `_hex16`: переходы по +именам публичных функций. Разъезжается на hex8.c/hex16.c/hex32.c без правок. +`_hex8_digit` — self-call внутри hex8, не мешает. + +### 3. conio/conio.c — jp _clrscr_attr (чист) + +`clrscr` → `jp _clrscr_attr` — tail-call публичной функции, работает через +модули как есть. + +### 4. mem/mem_bios.c, mem/mem_estex.c — jp __errno_set (чист) + +Tail-call публичного internal-хелпера `_errno_set` — кросс-модульный уже сейчас. + +--- +Все прочие `jr`-переходы в libc (проверены все `jr`, включая условные формы, +2026-07-05) ведут на метки внутри своей же функции — сплиту не мешают. diff --git a/examples/mdview/SAMPLE.MD b/examples/mdview/SAMPLE.MD new file mode 100644 index 0000000..130155c --- /dev/null +++ b/examples/mdview/SAMPLE.MD @@ -0,0 +1,5 @@ +MDVIEW Sample Document + +This is a sample Markdown file for testing the Sprinter *mdview text +viewer*. Phase 3 adds inline emphasis: bold, italic and underscore +runs render with distinct background colours. diff --git a/lib/Makefile b/lib/Makefile index e1570fc..dd4ae68 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -20,52 +20,10 @@ CC_FLAGS := -mz80 --no-std-crt0 --std-c99 --opt-code-size $(INC) BUILD := $(PROJ_ROOT)/lib/build -# All libc C modules. -LIBC_C := \ - libc/sys/atexit.c \ - libc/conio/conio.c \ - libc/conio/cprintf.c \ - libc/conio/text_palette.c \ - libc/bios/text.c \ - libc/io/dir.c \ - libc/video/videomode_raw.c \ - libc/video/palette.c \ - libc/errno/_errno_set.c \ - libc/env/env.c \ - libc/errno/errno.c \ - libc/io/fsdir.c \ - libc/io/lseek.c \ - libc/mouse/mouse.c \ - libc/io/open.c \ - libc/io/read.c \ - libc/time/sleep.c \ - libc/time/time.c \ - libc/time/posix_time.c \ - libc/io/unlink.c \ - libc/io/stat.c \ - libc/mem/bank_io_w3.c \ - libc/mem/bank_io_w1.c \ - libc/mem/mem_estex.c \ - libc/mem/mem_bios.c \ - libc/gfx/gfx_core.c \ - libc/gfx/gfx_palette.c \ - libc/gfx/gfx_raw_common.c \ - libc/gfx/gfx_raw_256.c \ - libc/gfx/gfx_raw_16.c \ - libc/gfx/gfx_256.c \ - libc/gfx/gfx_16.c \ - libc/gfx/gfx_font.c \ - libc/gfx/gfx_text_256.c \ - libc/gfx/gfx_text_16.c \ - libc/stdio/getchar.c \ - libc/stdio/putchar.c \ - libc/stdio/puts.c \ - libc/stdlib/minmax.c \ - libc/file/file.c \ - libc/stdio/hex_print.c \ - libc/stdio/dec_print.c \ - libc/string/strlwr.c \ - libc/string/strupr.c +# All libc C modules — every .c under libc// becomes its own .rel. +# One PUBLIC function per file (see docs/libc-split-asm-cases.md): the +# linker pulls whole .rel modules, so file granularity == DCE granularity. +LIBC_C := $(patsubst $(PROJ_ROOT)/%,%,$(wildcard $(PROJ_ROOT)/libc/*/*.c)) # Runtime modules to bundle (pulled by symbol references from libc-using code). # NOTE: runtime/bank.s is NOT bundled — its trampoline depends on the banking diff --git a/libc/bios/bios_clearwin.c b/libc/bios/bios_clearwin.c new file mode 100644 index 0000000..2461ce5 --- /dev/null +++ b/libc/bios/bios_clearwin.c @@ -0,0 +1,33 @@ +/* + * bios_clearwin — очистить окно (row, col, height, width) пробелами с + * атрибутом attr. BIOS LP_CLS_WIN ($89, rst 8): D=row, E=col, + * H=height, L=width, B=attr. + */ + +#include + +void bios_clearwin(uint8_t row, uint8_t col, uint8_t height, uint8_t width, + uint8_t attr) __naked +{ + (void)row; (void)col; (void)height; (void)width; (void)attr; + __asm + ;; row→A, col→L, height/width/attr на стеке [SP+2..4]. + ld iy, #2 + add iy, sp + ld h, 0 (iy) ; H = height + ld c, 1 (iy) ; спрятать width (L пока держит col) + ld e, l ; E = col + ld d, a ; D = row + ld l, c ; L = width + ld b, 2 (iy) ; B = attr + push ix + ld c, #0x89 + rst #0x08 + pop ix + pop hl + inc sp + inc sp + inc sp ; съесть height + width + attr + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_clearwin_ch.c b/libc/bios/bios_clearwin_ch.c new file mode 100644 index 0000000..6d34336 --- /dev/null +++ b/libc/bios/bios_clearwin_ch.c @@ -0,0 +1,35 @@ +/* + * bios_clearwin_ch — залить окно (row, col, height, width) символом + * fillch с атрибутом attr. BIOS LP_CLS_WIN2 ($8D, rst 8): D=row, + * E=col, H=height, L=width, B=attr, A=fillch. + */ + +#include + +void bios_clearwin_ch(uint8_t row, uint8_t col, uint8_t height, + uint8_t width, uint8_t attr, char fillch) __naked +{ + (void)row; (void)col; (void)height; (void)width; (void)attr; (void)fillch; + __asm + ;; row→A, col→L, height/width/attr/fillch на стеке [SP+2..5]. + ld iy, #2 + add iy, sp + ld d, a ; D = row (освободить A под fillch) + ld e, l ; E = col (освободить L под width) + ld h, 0 (iy) ; H = height + ld c, 1 (iy) ; спрятать width + ld b, 2 (iy) ; B = attr + ld a, 3 (iy) ; A = fillch + ld l, c ; L = width + push ix + ld c, #0x8D + rst #0x08 + pop ix + pop hl + inc sp + inc sp + inc sp + inc sp ; съесть height + width + attr + fillch + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_fillattr.c b/libc/bios/bios_fillattr.c new file mode 100644 index 0000000..832d8f1 --- /dev/null +++ b/libc/bios/bios_fillattr.c @@ -0,0 +1,20 @@ +/* + * bios_fillattr — залить count позиций атрибутом attr (символы не + * трогаются) от текущего place. BIOS LP_PRINT_ATR ($83, rst 8): + * E=attr, B=count. Сохраняет HL и IX. + */ + +#include + +void bios_fillattr(uint8_t attr, uint8_t count) __naked +{ + (void)attr; (void)count; + __asm + ;; attr→A, count→L. + ld e, a + ld b, l + ld c, #0x83 + rst #0x08 + ret + __endasm; +} diff --git a/libc/bios/bios_fillchar.c b/libc/bios/bios_fillchar.c new file mode 100644 index 0000000..9a20ca4 --- /dev/null +++ b/libc/bios/bios_fillchar.c @@ -0,0 +1,19 @@ +/* + * bios_fillchar — вывести символ ch count раз (атрибут не трогается) + * от текущего place. BIOS LP_PRINT_SYM ($82, rst 8): A=ch, B=count. + * Сохраняет HL и IX. + */ + +#include + +void bios_fillchar(char ch, uint8_t count) __naked +{ + (void)ch; (void)count; + __asm + ;; ch→A, count→L. + ld b, l + ld c, #0x82 + rst #0x08 + ret + __endasm; +} diff --git a/libc/bios/bios_fillcharattr.c b/libc/bios/bios_fillcharattr.c new file mode 100644 index 0000000..6bdf18d --- /dev/null +++ b/libc/bios/bios_fillcharattr.c @@ -0,0 +1,26 @@ +/* + * bios_fillcharattr — вывести символ ch с атрибутом attr count раз от + * текущего place. BIOS LP_PRINT_ALL ($81, rst 8): A=ch, E=attr, + * B=count. Сохраняет HL и IX — push/pop ix не нужен. + * + * SDCC __sdcccall(1): ch→A, attr→L, count на стеке [SP+2]; стековый + * байт съедает callee (inc sp), IY — скретч для чтения без порчи IX. + */ + +#include + +void bios_fillcharattr(char ch, uint8_t attr, uint8_t count) __naked +{ + (void)ch; (void)attr; (void)count; + __asm + ld iy, #2 + add iy, sp + ld b, 0 (iy) ; B = count + ld e, l ; E = attr + ld c, #0x81 + rst #0x08 + pop hl ; адрес возврата + inc sp ; съесть байт count + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_get_place.c b/libc/bios/bios_get_place.c new file mode 100644 index 0000000..928b2ee --- /dev/null +++ b/libc/bios/bios_get_place.c @@ -0,0 +1,16 @@ +/* + * bios_get_place — текущая позиция BIOS-вывода: (row << 8) | col. + * BIOS LP_GET_PLACE ($8E, rst 8) возвращает D=row, E=col — это уже + * конвенция возврата uint16_t (DE), перестановок не нужно. + */ + +#include + +uint16_t bios_get_place(void) __naked +{ + __asm + ld c, #0x8E + rst #0x08 + ret + __endasm; +} diff --git a/libc/bios/bios_scrollwin.c b/libc/bios/bios_scrollwin.c new file mode 100644 index 0000000..bb8f239 --- /dev/null +++ b/libc/bios/bios_scrollwin.c @@ -0,0 +1,27 @@ +/* + * bios_scrollwin — прокрутить экран от строки row на count строк. + * BIOS LP_SCROLL_UD ($8A, rst 8): B=dir (1=вверх, 2=вниз), D=row, + * E=count. + */ + +#include + +void bios_scrollwin(uint8_t dir, uint8_t row, uint8_t count) __naked +{ + (void)dir; (void)row; (void)count; + __asm + ;; dir→A, row→L, count на стеке [SP+2]. + ld iy, #2 + add iy, sp + ld e, 0 (iy) ; E = count + ld d, l ; D = row + ld b, a ; B = dir + push ix + ld c, #0x8A + rst #0x08 + pop ix + pop hl + inc sp + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_set_place.c b/libc/bios/bios_set_place.c new file mode 100644 index 0000000..db4fc27 --- /dev/null +++ b/libc/bios/bios_set_place.c @@ -0,0 +1,20 @@ +/* + * bios_set_place — установить позицию вывода BIOS-текста (place). + * BIOS LP_SET_PLACE ($84, rst 8): D=row, E=col. Используется без + * push/pop ix (как gotoxy в conio — эмпирически безопасно). + */ + +#include + +void bios_set_place(uint8_t row, uint8_t col) __naked +{ + (void)row; (void)col; + __asm + ;; row→A, col→L. + ld d, a + ld e, l + ld c, #0x84 + rst #0x08 + ret + __endasm; +} diff --git a/libc/bios/bios_write.c b/libc/bios/bios_write.c new file mode 100644 index 0000000..ace1d52 --- /dev/null +++ b/libc/bios/bios_write.c @@ -0,0 +1,25 @@ +/* + * bios_write — вывести len символов строки s (атрибут не трогается). + * BIOS LP_PRINT_LN2 ($86, rst 8): HL=s, B=len. Place продвигается. + * s должен лежать в #4000-#BFFF. + */ + +#include + +void bios_write(const char *s, uint8_t len) __naked +{ + (void)s; (void)len; + __asm + ;; s→HL, len на стеке [SP+2]. + ld iy, #2 + add iy, sp + ld b, 0 (iy) ; B = len + push ix + ld c, #0x86 + rst #0x08 + pop ix + pop hl + inc sp + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_write_stop.c b/libc/bios/bios_write_stop.c new file mode 100644 index 0000000..dd639d8 --- /dev/null +++ b/libc/bios/bios_write_stop.c @@ -0,0 +1,27 @@ +/* + * bios_write_stop — вывести строку s до разделителя sep БЕЗ добивки + * (максимум maxlen символов, атрибут не трогается). + * BIOS LP_PRINT_LN6 ($8C, rst 8): HL=s, B=maxlen, D=sep. + */ + +#include + +void bios_write_stop(const char *s, uint8_t maxlen, char sep) __naked +{ + (void)s; (void)maxlen; (void)sep; + __asm + ;; s→HL, maxlen/sep на стеке [SP+2..3]. + ld iy, #2 + add iy, sp + ld b, 0 (iy) ; B = maxlen + ld d, 1 (iy) ; D = sep + push ix + ld c, #0x8C + rst #0x08 + pop ix + pop hl + inc sp + inc sp + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_write_until.c b/libc/bios/bios_write_until.c new file mode 100644 index 0000000..53eef91 --- /dev/null +++ b/libc/bios/bios_write_until.c @@ -0,0 +1,27 @@ +/* + * bios_write_until — вывести строку s до разделителя sep, ДОБИВ + * пробелами до len (атрибут не трогается). BIOS LP_PRINT_LN4 ($88, + * rst 8): HL=s, B=len, D=sep. + */ + +#include + +void bios_write_until(const char *s, uint8_t len, char sep) __naked +{ + (void)s; (void)len; (void)sep; + __asm + ;; s→HL, len/sep на стеке [SP+2..3]. + ld iy, #2 + add iy, sp + ld b, 0 (iy) ; B = len + ld d, 1 (iy) ; D = sep + push ix + ld c, #0x88 + rst #0x08 + pop ix + pop hl + inc sp + inc sp + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_writeattr.c b/libc/bios/bios_writeattr.c new file mode 100644 index 0000000..dc29760 --- /dev/null +++ b/libc/bios/bios_writeattr.c @@ -0,0 +1,29 @@ +/* + * bios_writeattr — вывести len символов строки s с атрибутом attr. + * BIOS LP_PRINT_LN ($85, rst 8): HL=s, B=len, E=attr. Place + * продвигается после печати. s должен лежать в #4000-#BFFF. + * + * SDCC __sdcccall(1): s→HL, len/attr на стеке [SP+2]/[SP+3]; + * стековые байты съедает callee. + */ + +#include + +void bios_writeattr(const char *s, uint8_t len, uint8_t attr) __naked +{ + (void)s; (void)len; (void)attr; + __asm + ld iy, #2 + add iy, sp + ld b, 0 (iy) ; B = len + ld e, 1 (iy) ; E = attr + push ix + ld c, #0x85 + rst #0x08 + pop ix + pop hl ; адрес возврата + inc sp + inc sp ; съесть len + attr + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_writeattr_stop.c b/libc/bios/bios_writeattr_stop.c new file mode 100644 index 0000000..64c9264 --- /dev/null +++ b/libc/bios/bios_writeattr_stop.c @@ -0,0 +1,30 @@ +/* + * bios_writeattr_stop — вывести строку s с атрибутом attr до + * разделителя sep БЕЗ добивки пробелами (максимум maxlen символов). + * BIOS LP_PRINT_LN5 ($8B, rst 8): HL=s, B=maxlen, E=attr, D=sep. + */ + +#include + +void bios_writeattr_stop(const char *s, uint8_t maxlen, uint8_t attr, + char sep) __naked +{ + (void)s; (void)maxlen; (void)attr; (void)sep; + __asm + ;; s→HL, maxlen/attr/sep на стеке [SP+2..4]. + ld iy, #2 + add iy, sp + ld b, 0 (iy) ; B = maxlen + ld e, 1 (iy) ; E = attr + ld d, 2 (iy) ; D = sep + push ix + ld c, #0x8B + rst #0x08 + pop ix + pop hl + inc sp + inc sp + inc sp + jp (hl) + __endasm; +} diff --git a/libc/bios/bios_writeattr_until.c b/libc/bios/bios_writeattr_until.c new file mode 100644 index 0000000..1c26734 --- /dev/null +++ b/libc/bios/bios_writeattr_until.c @@ -0,0 +1,29 @@ +/* + * bios_writeattr_until — вывести строку s с атрибутом attr до + * разделителя sep, ДОБИВ пробелами до len. BIOS LP_PRINT_LN3 ($87, + * rst 8): HL=s, B=len, E=attr, D=sep. + */ + +#include + +void bios_writeattr_until(const char *s, uint8_t len, uint8_t attr, char sep) __naked +{ + (void)s; (void)len; (void)attr; (void)sep; + __asm + ;; s→HL, len/attr/sep на стеке [SP+2..4]. + ld iy, #2 + add iy, sp + ld b, 0 (iy) ; B = len + ld e, 1 (iy) ; E = attr + ld d, 2 (iy) ; D = sep + push ix + ld c, #0x87 + rst #0x08 + pop ix + pop hl + inc sp + inc sp + inc sp ; съесть len + attr + sep + jp (hl) + __endasm; +} diff --git a/libc/bios/text.c b/libc/bios/text.c deleted file mode 100644 index 4c58a75..0000000 --- a/libc/bios/text.c +++ /dev/null @@ -1,302 +0,0 @@ -/* - * text.c — wrappers for the Sprinter BIOS text-output calls (RST 8, - * 081h..08Eh "LP_*"). See for the full opcode -> name - * mapping and semantics. - * - * ABI recap (docs/converted/bios.txt): - * - 081h/082h/083h explicitly preserve HL and IX -> no push/pop ix - * needed, and the result is the smallest/fastest of the set. - * - 084h/08Eh (place get/set) are used without push/pop ix elsewhere - * in this libc (libc/conio/conio.c gotoxy/wherex/wherey via the same - * opcodes) — empirically safe, followed here too. - * - Everything else clobbers IX like any other BIOS/ESTEX call, so - * every RST 8 below is bracketed with push ix / pop ix. - * - * Stack-argument layout: SDCC __sdcccall(1) places the first 8-bit arg - * in A, the second (only if the first was also 8-bit) in L; every arg - * beyond that lands on the stack in left-to-right declaration order, - * starting at [SP+2] (right after the 2-byte return address) — verified - * empirically via `sdcc -S` for 2..6 uint8 args and for pointer+N uint8 - * args. IY is used as a scratch index register to read those without - * disturbing IX (the caller's frame pointer). - */ - -#include - -void bios_fillcharattr(char ch, uint8_t attr, uint8_t count) __naked -{ - (void)ch; (void)attr; (void)count; - __asm - ;; ch->A, attr->L, count on stack at [SP+2]. - ;; LP_PRINT_ALL (081h): A=ch, E=attr, B=count. Preserves HL,IX. - ld iy, #2 - add iy, sp - ld b, 0 (iy) ; B = count - ld e, l ; E = attr - ld c, #0x81 - rst #0x08 - pop hl ; return address - inc sp ; consume count byte - jp (hl) - __endasm; -} - -void bios_fillchar(char ch, uint8_t count) __naked -{ - (void)ch; (void)count; - __asm - ;; ch->A, count->L. LP_PRINT_SYM (082h): A=ch, B=count. - ;; Preserves HL,IX. - ld b, l - ld c, #0x82 - rst #0x08 - ret - __endasm; -} - -void bios_fillattr(uint8_t attr, uint8_t count) __naked -{ - (void)attr; (void)count; - __asm - ;; attr->A, count->L. LP_PRINT_ATR (083h): E=attr, B=count. - ;; Preserves HL,IX. - ld e, a - ld b, l - ld c, #0x83 - rst #0x08 - ret - __endasm; -} - -void bios_set_place(uint8_t row, uint8_t col) __naked -{ - (void)row; (void)col; - __asm - ;; row->A, col->L. LP_SET_PLACE (084h): D=row, E=col. - ld d, a - ld e, l - ld c, #0x84 - rst #0x08 - ret - __endasm; -} - -uint16_t bios_get_place(void) __naked -{ - __asm - ;; LP_GET_PLACE (08Eh): returns D=row, E=col — already the - ;; uint16_t return convention (DE); nothing left to rearrange. - ld c, #0x8E - rst #0x08 - ret - __endasm; -} - -void bios_writeattr(const char *s, uint8_t len, uint8_t attr) __naked -{ - (void)s; (void)len; (void)attr; - __asm - ;; s->HL, len/attr on stack at [SP+2]/[SP+3]. - ;; LP_PRINT_LN (085h): HL=s, B=len, E=attr. - ld iy, #2 - add iy, sp - ld b, 0 (iy) ; B = len - ld e, 1 (iy) ; E = attr - push ix - ld c, #0x85 - rst #0x08 - pop ix - pop hl ; return address - inc sp - inc sp ; consume len + attr - jp (hl) - __endasm; -} - -void bios_write(const char *s, uint8_t len) __naked -{ - (void)s; (void)len; - __asm - ;; s->HL, len on stack at [SP+2]. - ;; LP_PRINT_LN2 (086h): HL=s, B=len. - ld iy, #2 - add iy, sp - ld b, 0 (iy) ; B = len - push ix - ld c, #0x86 - rst #0x08 - pop ix - pop hl - inc sp - jp (hl) - __endasm; -} - -void bios_writeattr_until(const char *s, uint8_t len, uint8_t attr, char sep) __naked -{ - (void)s; (void)len; (void)attr; (void)sep; - __asm - ;; s->HL, len/attr/sep on stack at [SP+2..4]. - ;; LP_PRINT_LN3 (087h): HL=s, B=len, E=attr, D=sep. - ;; Pads with spaces after sep up to len. - ld iy, #2 - add iy, sp - ld b, 0 (iy) ; B = len - ld e, 1 (iy) ; E = attr - ld d, 2 (iy) ; D = sep - push ix - ld c, #0x87 - rst #0x08 - pop ix - pop hl - inc sp - inc sp - inc sp ; consume len + attr + sep - jp (hl) - __endasm; -} - -void bios_write_until(const char *s, uint8_t len, char sep) __naked -{ - (void)s; (void)len; (void)sep; - __asm - ;; s->HL, len/sep on stack at [SP+2..3]. - ;; LP_PRINT_LN4 (088h): HL=s, B=len, D=sep. Pads with spaces. - ld iy, #2 - add iy, sp - ld b, 0 (iy) ; B = len - ld d, 1 (iy) ; D = sep - push ix - ld c, #0x88 - rst #0x08 - pop ix - pop hl - inc sp - inc sp - jp (hl) - __endasm; -} - -void bios_clearwin(uint8_t row, uint8_t col, uint8_t height, uint8_t width, - uint8_t attr) __naked -{ - (void)row; (void)col; (void)height; (void)width; (void)attr; - __asm - ;; row->A, col->L, height/width/attr on stack at [SP+2..4]. - ;; LP_CLS_WIN (089h): D=row, E=col, H=height, L=width, B=attr. - ld iy, #2 - add iy, sp - ld h, 0 (iy) ; H = height - ld c, 1 (iy) ; stash width (L is still col for now) - ld e, l ; E = col - ld d, a ; D = row - ld l, c ; L = width - ld b, 2 (iy) ; B = attr - push ix - ld c, #0x89 - rst #0x08 - pop ix - pop hl - inc sp - inc sp - inc sp ; consume height + width + attr - jp (hl) - __endasm; -} - -void bios_scrollwin(uint8_t dir, uint8_t row, uint8_t count) __naked -{ - (void)dir; (void)row; (void)count; - __asm - ;; dir->A, row->L, count on stack at [SP+2]. - ;; LP_SCROLL_UD (08Ah): B=dir(1 up/2 down), D=row, E=count. - ld iy, #2 - add iy, sp - ld e, 0 (iy) ; E = count - ld d, l ; D = row - ld b, a ; B = dir - push ix - ld c, #0x8A - rst #0x08 - pop ix - pop hl - inc sp - jp (hl) - __endasm; -} - -void bios_writeattr_stop(const char *s, uint8_t maxlen, uint8_t attr, - char sep) __naked -{ - (void)s; (void)maxlen; (void)attr; (void)sep; - __asm - ;; s->HL, maxlen/attr/sep on stack at [SP+2..4]. - ;; LP_PRINT_LN5 (08Bh): HL=s, B=maxlen, E=attr, D=sep. - ;; Stops at sep (no padding). - ld iy, #2 - add iy, sp - ld b, 0 (iy) ; B = maxlen - ld e, 1 (iy) ; E = attr - ld d, 2 (iy) ; D = sep - push ix - ld c, #0x8B - rst #0x08 - pop ix - pop hl - inc sp - inc sp - inc sp - jp (hl) - __endasm; -} - -void bios_write_stop(const char *s, uint8_t maxlen, char sep) __naked -{ - (void)s; (void)maxlen; (void)sep; - __asm - ;; s->HL, maxlen/sep on stack at [SP+2..3]. - ;; LP_PRINT_LN6 (08Ch): HL=s, B=maxlen, D=sep. Stops at sep. - ld iy, #2 - add iy, sp - ld b, 0 (iy) ; B = maxlen - ld d, 1 (iy) ; D = sep - push ix - ld c, #0x8C - rst #0x08 - pop ix - pop hl - inc sp - inc sp - jp (hl) - __endasm; -} - -void bios_clearwin_ch(uint8_t row, uint8_t col, uint8_t height, - uint8_t width, uint8_t attr, char fillch) __naked -{ - (void)row; (void)col; (void)height; (void)width; (void)attr; (void)fillch; - __asm - ;; row->A, col->L, height/width/attr/fillch on stack [SP+2..5]. - ;; LP_CLS_WIN2 (08Dh): D=row, E=col, H=height, L=width, B=attr, - ;; A=fillch. - ld iy, #2 - add iy, sp - ld d, a ; D = row (free A for fillch later) - ld e, l ; E = col (free L for width later) - ld h, 0 (iy) ; H = height - ld c, 1 (iy) ; stash width - ld b, 2 (iy) ; B = attr - ld a, 3 (iy) ; A = fillch - ld l, c ; L = width - push ix - ld c, #0x8D - rst #0x08 - pop ix - pop hl - inc sp - inc sp - inc sp - inc sp ; consume height + width + attr + fillch - jp (hl) - __endasm; -} diff --git a/libc/conio/_bios_putchar.c b/libc/conio/_bios_putchar.c new file mode 100644 index 0000000..4dccbfe --- /dev/null +++ b/libc/conio/_bios_putchar.c @@ -0,0 +1,21 @@ +/* + * _bios_putchar — быстрый вывод символа через ESTEX PUTCHAR ($5B): + * без атрибута, CR/LF/скролл и курсор ведёт сам ESTEX. Используется + * (задуман) для пути g_text_attr == KEEP_EXIST_ATTR. + * PUTCHAR не меняет IX (проверено), поэтому push/pop ix не нужен. + */ + +#include "_conio.h" + +char _bios_putchar(char ch) __naked +{ + (void)ch; + __asm + ;; ch в A. push af сохраняет его через RST (который портит A). + push af + ld c, #0x5B ; ESTEX PUTCHAR + rst #0x10 + pop af + ret + __endasm; +} diff --git a/libc/conio/_conio.h b/libc/conio/_conio.h new file mode 100644 index 0000000..34c984f --- /dev/null +++ b/libc/conio/_conio.h @@ -0,0 +1,49 @@ +/* + * _conio.h — внутренние состояние и хелперы conio. + * + * НЕ публичный заголовок: живёт рядом с исходниками. После сплита + * «1 функция = 1 модуль» каждый элемент лежит в своём модуле, чтобы + * приложение тянуло из архива только то, что реально вызывает: + * + * _g_text_attr.c — g_text_attr (текущий атрибут, KEEP_EXIST_ATTR) + * _pc_place.c — pc_place (курсор col:row; global — читают тесты) + * _pc_raw_mode.c — pc_raw_mode (режим управляющих символов putch) + * _get_cursor.c — чтение курсора BIOS → pc_place + * _set_cursor.c — запись pc_place → курсор BIOS + * _putch_wrchar.c — воркер вывода mode-0 (BS/TAB/LF/CR интерпретируются) + * _putch_wrchar_raw.c — воркер mode-1 (всё как глифы) + * _bios_putchar.c — быстрый вывод через ESTEX PUTCHAR (без атрибута) + */ +#ifndef _CONIO_INTERNAL_H +#define _CONIO_INTERNAL_H + +#include /* two_bytes, KEEP_EXIST_ATTR */ +#include + +/* Текущий текстовый атрибут: 0x00..0xFF — реальный байт атрибута, + * KEEP_EXIST_ATTR (0xFFFF) — быстрый путь без атрибута. */ +extern two_bytes g_text_attr; + +/* Позиция курсора: .byte.low = col, .byte.high = row. + * Поддерживается gotoxy/wherex/wherey/wherexy и воркерами putch. */ +extern two_bytes pc_place; + +/* 0 — BS/TAB/LF/CR интерпретируются; 1 — печатаются как глифы. */ +extern uint8_t pc_raw_mode; + +/* Прочитать курсор BIOS (rst 8, $8E) в pc_place. */ +void _get_cursor(void); + +/* Установить курсор BIOS (rst 8, $84) из pc_place. */ +void _set_cursor(void); + +/* Быстрый вывод символа через ESTEX PUTCHAR ($5B) — атрибут не + * трогается, CR/LF/скролл делает ESTEX. */ +char _bios_putchar(char ch); + +/* Воркеры вывода в (pc_place) атрибутом attr; курсор BIOS НЕ трогают — + * вызывающий делает _get_cursor()/_set_cursor() один раз на операцию. */ +void _putch_wrchar(char ch, uint8_t attr); +void _putch_wrchar_raw(char ch, uint8_t attr); + +#endif diff --git a/libc/conio/_g_text_attr.c b/libc/conio/_g_text_attr.c new file mode 100644 index 0000000..f6d004c --- /dev/null +++ b/libc/conio/_g_text_attr.c @@ -0,0 +1,12 @@ +/* + * g_text_attr — текущий текстовый атрибут conio (Turbo-C-стиль: stdio + * putchar/puts быстрые и без атрибута; putch/cputs/cprintf применяют + * этот атрибут). Модуль только с данными. + * + * 0x00..0xFF — реальный атрибут (4 бита FG | 3 бита BG | 1 бит blink) + * KEEP_EXIST_ATTR (0xFFFF) — putch/cputs уходят на быстрый путь + */ + +#include "_conio.h" + +two_bytes g_text_attr; diff --git a/libc/conio/_get_cursor.c b/libc/conio/_get_cursor.c new file mode 100644 index 0000000..1167fe8 --- /dev/null +++ b/libc/conio/_get_cursor.c @@ -0,0 +1,16 @@ +/* + * _get_cursor — прочитать текущий курсор BIOS GetCursor (rst 8, $8E) + * в pc_place (D=row, E=col → сохраняются парой). + */ + +#include "_conio.h" + +void _get_cursor(void) __naked +{ + __asm + ld c, #0x8e ; BIOS GetCursor + rst #0x08 + ld (_pc_place), de + ret + __endasm; +} diff --git a/libc/conio/_pc_place.c b/libc/conio/_pc_place.c new file mode 100644 index 0000000..acdf29c --- /dev/null +++ b/libc/conio/_pc_place.c @@ -0,0 +1,10 @@ +/* + * pc_place — кэш позиции курсора conio: .byte.low = col (0..79), + * .byte.high = row (0..31). Обновляется gotoxy/wherex/wherey/wherexy + * и воркерами putch; глобальный (не `_`-имя) — читается и тестами + * (tests/conio2). Модуль только с данными. + */ + +#include "_conio.h" + +two_bytes pc_place; diff --git a/libc/conio/_pc_raw_mode.c b/libc/conio/_pc_raw_mode.c new file mode 100644 index 0000000..15208dd --- /dev/null +++ b/libc/conio/_pc_raw_mode.c @@ -0,0 +1,13 @@ +/* + * pc_raw_mode — режим обработки управляющих символов (< 0x20) в + * воркерах putch/cputs: + * 0 (по умолчанию) — BS/TAB/LF/CR интерпретируются (без глифа); + * 1 — все символы печатаются как глифы CP437. + * Действует только на WRCHAR-пути (attr ≤ 0xFF); при KEEP_EXIST_ATTR + * курсором и управляющими символами занимается сам ESTEX. + * Модуль только с данными. + */ + +#include "_conio.h" + +uint8_t pc_raw_mode; diff --git a/libc/conio/_putch_wrchar.c b/libc/conio/_putch_wrchar.c new file mode 100644 index 0000000..a3b2d38 --- /dev/null +++ b/libc/conio/_putch_wrchar.c @@ -0,0 +1,98 @@ +/* + * _putch_wrchar — воркер вывода mode-0: пишет символ в (pc_place) + * через ESTEX WRCHAR ($58) атрибутом attr, интерпретируя управляющие: + * + * 0x08 BS → pc_col-- (если не 0) + * 0x09 TAB → pc_col к следующему кратному 8 (потолок 80) + * 0x0A LF → pc_row++ (потолок 32; без глифа) + * 0x0D CR → pc_col = 0 + * прочее → WRCHAR + pc_col++ + * + * WRCHAR подавляется при pc_col ≥ 80 или pc_row ≥ 32 (вне экрана) — + * координаты только [0..79] × [0..31]. + * + * Курсор BIOS НЕ трогает — вызывающий читает его один раз до серии + * вызовов и записывает один раз после: плата BIOS за ОПЕРАЦИЮ, + * а не за символ. + */ + +#include "_conio.h" + +void _putch_wrchar(char ch, uint8_t attr) __naked +{ + (void)ch; (void)attr; + __asm + ;; __sdcccall(1): ch в A, attr в L. + ;; Диспетчеризация по управляющим, пока A держит ch (cp не + ;; трогает A). B/C грузятся только на пути вывода, поэтому + ;; ветки управляющих символов дешевле. + cp #0x08 + jr z, _rp0_bs + cp #0x09 + jr z, _rp0_tab + cp #0x0A + jr z, _rp0_lf + cp #0x0D + jr z, _rp0_cr + ;; Всё остальное (печатное или незнакомый ctrl) → глиф. + + _rp0_pri: + + ld c, a ; C = ch (сохранить до порчи A) + ld a, (_pc_place + 1) + cp #32 + ret nc ; за нижним краем — молча пропустить + ld d, a ; D = row (конвенция ESTEX WRCHAR) + ld a, (_pc_place) + cp #80 + ret nc ; за правым краем — молча пропустить + ld e, a ; E = col + inc a + ld (_pc_place), a ; pc_col++ + + _rp0_wr: + ld b, l ; B = attr + ld a, c ; A = ch + push ix + ld c, #0x58 ; ESTEX WRCHAR + rst #0x10 + pop ix + ret + + _rp0_bs: + ld a, (_pc_place) + or a, a + ret z ; уже в колонке 0 — без изменений + dec a + ld (_pc_place), a + ld e, a ; E = col + ld a, (_pc_place + 1) + ld d, a ; D = row (конвенция ESTEX WRCHAR) + ld c, #0x20 + jr _rp0_wr + + _rp0_tab: + ld a, (_pc_place) + or #0x07 ; округлить вниз к кратному 8 + inc a ; → следующее кратное 8 + cp #81 ; сравнить A с 81 (0x51) + jr c, _rp0_tab_skip ; если A < 81 (т.е. A ≤ 80), пропустить загрузку + ld a, #80 ; иначе A > 80 → установить A = 80 + _rp0_tab_skip: + ld (_pc_place), a + ret + + _rp0_lf: + ld a, (_pc_place + 1) + cp #32 + ret nc ; уже у нижнего края + inc a + ld (_pc_place + 1), a + ret + + _rp0_cr: + xor a, a + ld (_pc_place), a + ret + __endasm; +} diff --git a/libc/conio/_putch_wrchar_raw.c b/libc/conio/_putch_wrchar_raw.c new file mode 100644 index 0000000..58ee7c4 --- /dev/null +++ b/libc/conio/_putch_wrchar_raw.c @@ -0,0 +1,37 @@ +/* + * _putch_wrchar_raw — воркер вывода mode-1: КАЖДЫЙ байт идёт через + * ESTEX WRCHAR ($58) как глиф CP437 (включая 0x08/0x09/0x0A/0x0D), + * атрибутом attr, в позицию (pc_place) с pc_col++. + * Вне экрана (col ≥ 80 / row ≥ 32) вывод молча подавляется. + * Курсор BIOS не трогает — см. _putch_wrchar. + */ + +#include "_conio.h" + +void _putch_wrchar_raw(char ch, uint8_t attr) __naked +{ + (void)ch; (void)attr; + __asm + ;; __sdcccall(1): ch в A, attr в L. + ld c, a ; C = ch (сохранить) + + ld a, (_pc_place + 1) + cp #32 + ret nc ; за нижним краем — молча пропустить + ld d, a ; D = row + ld a, (_pc_place) + cp #80 + ret nc ; за правым краем — молча пропустить + ld e, a ; E = col + inc a + ld (_pc_place), a ; pc_col++ + + ld b, l ; B = attr + ld a, c ; A = ch + push ix + ld c, #0x58 ; ESTEX WRCHAR + rst #0x10 + pop ix + ret + __endasm; +} diff --git a/libc/conio/_set_cursor.c b/libc/conio/_set_cursor.c new file mode 100644 index 0000000..dbfe25a --- /dev/null +++ b/libc/conio/_set_cursor.c @@ -0,0 +1,16 @@ +/* + * _set_cursor — установить курсор BIOS SetCursor (rst 8, $84) из + * pc_place (D=row, E=col). + */ + +#include "_conio.h" + +void _set_cursor(void) __naked +{ + __asm + ld de, (_pc_place) + ld c, #0x84 ; BIOS SetCursor + rst #0x08 + ret + __endasm; +} diff --git a/libc/conio/cgets.c b/libc/conio/cgets.c new file mode 100644 index 0000000..034b088 --- /dev/null +++ b/libc/conio/cgets.c @@ -0,0 +1,33 @@ +/* + * cgets — построчный ввод в стиле Solid-C / Turbo-C: + * buf[0] = максимум символов (вход) + * buf[1] = фактическое количество (выход) + * buf[2..] = символы + NUL + * Ввод через getche() (с эхом), Enter завершает строку (выводится + * "\r\n"), Backspace откатывает символ. Возвращает &buf[2]. + */ + +#include +#include + +char *cgets(char *buf) +{ + uint8_t maxlen = (uint8_t)buf[0]; + uint8_t n = 0; + while (n < maxlen) { + int ch = getche(); + if (ch == '\n' || ch == '\r') { + putch('\r'); putch('\n'); + break; + } + if (ch == 8) { /* backspace */ + if (n > 0) { n--; } + continue; + } + buf[2 + n] = (char)ch; + n++; + } + buf[1] = (char)n; + buf[2 + n] = 0; + return &buf[2]; +} diff --git a/libc/conio/clrscr.c b/libc/conio/clrscr.c new file mode 100644 index 0000000..76ced2f --- /dev/null +++ b/libc/conio/clrscr.c @@ -0,0 +1,15 @@ +/* + * clrscr — очистить экран атрибутом по умолчанию 0x0F (ярко-белый на + * чёрном). Tail-call в clrscr_attr (jp по глобальному имени — работает + * через границу модулей). + */ + +#include "_conio.h" + +void clrscr(void) __naked +{ + __asm + ld a, #0x0F + jp _clrscr_attr + __endasm; +} diff --git a/libc/conio/clrscr_attr.c b/libc/conio/clrscr_attr.c new file mode 100644 index 0000000..2e3238a --- /dev/null +++ b/libc/conio/clrscr_attr.c @@ -0,0 +1,24 @@ +/* + * clrscr_attr — очистить экран (80×32) пробелами с заданным атрибутом. + * ESTEX CLEAR ($56): A=символ-заполнитель, B=атрибут, DE=верхний левый + * угол, H=строк, L=колонок. + */ + +#include "_conio.h" + +void clrscr_attr(uint8_t attr) __naked +{ + (void)attr; + __asm + push ix + ;; SDCC __sdcccall(1): первый uint8_t аргумент в A. + ld b, a ; B = атрибут (цвет заливки) + ld de, #0x0000 ; верхний левый угол + ld hl, #0x2050 ; H=32 строки, L=80 колонок + ld a, #0x20 ; заполнение пробелом + ld c, #0x56 ; ESTEX CLEAR + rst #0x10 + pop ix + ret + __endasm; +} diff --git a/libc/conio/conio.c b/libc/conio/conio.c deleted file mode 100644 index 6f37196..0000000 --- a/libc/conio/conio.c +++ /dev/null @@ -1,678 +0,0 @@ -/* - * conio.c — console I/O wrappers around ESTEX kbd/screen syscalls. - * - * $30 WAITKEY — blocking read, returns scan / ASCII / modifiers - * $31 SCANKEY — non-blocking poll - * $32 ECHOKEY — blocking read + auto-echo to the screen - * $52 LOCATE — set cursor to (D=row, E=col) - * $56 CLEAR — fill a window with (A=char, B=attr) - * $5B PUTCHAR — write single character (CR/LF/scroll handled by ESTEX) - * - * Every RST 10h is bracketed with push/pop IX (caller's frame pointer). - */ - -#include -#include -#include - - -/* Forward extern — definition is further down (after putch/cputs which - * reference it from asm by linker-symbol name). */ -static two_bytes g_text_attr = {0}; -static uint8_t pc_ch = 0; -extern two_bytes pc_place = {0}; -static uint8_t pc_raw_mode = 0; - - -// TODO - проверить - ф-ии 30h-33h (kbhit/getch/getche/getkey) -// не должны менять IX и им можно не делать push ix / pop ix -// -char kbhit(void) __naked -{ - __asm - push ix - ld c, #0x33 ; ESTEX CTRLKEY — peeks without consuming - rst #0x10 - pop ix - ;; A=0 → no key waiting; non-zero → there is one. - or a, a - ret z - ld a, #0x01 - ret - __endasm; -} - -char getch(void) __naked -{ - __asm - push ix - ld c, #0x30 ; ESTEX WAITKEY (no echo) - rst #0x10 - pop ix - ;; ESTEX returns ASCII in E (and copy in A) - ret - __endasm; -} - -char getche(void) __naked -{ - __asm - push ix - ld c, #0x32 ; ESTEX ECHOKEY (echo to console) - rst #0x10 - pop ix - ;; ESTEX returns ASCII in E (and copy in A) - ret - __endasm; -} - -/* getkey — like getch() but exposes BOTH the ASCII value and the - * positional scan code, so callers can distinguish extended keys - * (arrows, F1..F12, PgUp/PgDn, Home/End, Ins/Del — all of which carry - * ASCII == 0 from ESTEX) from plain ASCII keys. - * - * return = (scan << 8) | ascii - * - * For plain keys: ascii holds the character, scan holds the positional - * code (bit 7 set when Ctrl/Alt/Shift is held). - * For extended keys: ascii == 0, scan identifies the key (see KEY_* in - * ). - */ -uint16_t getkey(void) __naked -{ - __asm - push ix - ld c, #0x30 ; ESTEX WAITKEY: A=ASCII, D=scan, E=ASCII - rst #0x10 - pop ix - ld e, a ; E = ASCII (defensive: A is the canonical copy) - ret ; __sdcccall(1) returns uint16_t in DE - __endasm; -} - -/* ---- putch / cputs: Turbo-C conio convention ---------------------- * - * Both APPLY the current text attribute (g_text_attr). When attr is - * KEEP_EXIST_ATTR (>0xFF), they short-circuit to the FAST stdio path - * (putchar / puts-like raw PCHARS). - * - * No '\n' to CR LF translation here — Turbo-C cputs/putch require the - * caller to use "\r\n" explicitly. Stdio puts/putchar do translate. - */ - - -/* Controls how _raw_putch treats control characters (< 0x20): - * 0 (default) — BS/TAB/LF/CR are interpreted (no glyph output); - * other chars print as glyphs via WRCHAR. - * 1 — all characters print as glyphs, no interpretation. - * - * Only takes effect on the WRCHAR (attr ≤ 0xFF) path. When - * g_text_attr is KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS handle - * cursor and control chars — pc_raw_mode is irrelevant. */ - -void set_putch_raw_mode(uint8_t mode) { pc_raw_mode = mode; } -uint8_t get_putch_raw_mode(void) { return pc_raw_mode; } - -/* ---- Internal helpers ------------------------------------------- */ - -/* Read current cursor into pc_row / pc_col via ESTEX CURSOR ($53). */ -static void _get_cursor(void) __naked -{ - __asm - ; push ix - ld c, #0x8e ; BIOS GetCursor - rst #0x08 - ld (_pc_place), de - ; pop ix - ret - __endasm; -} - -/* Move cursor to (pc_col, pc_row) via ESTEX LOCATE ($52). */ -static void _set_cursor(void) __naked -{ - __asm - ; push ix - ld de, (_pc_place) - ld c, #0x84 ; BIOS SetCursor - rst #0x08 - ; pop ix - ret - __endasm; -} - -/* ESTEX PUTCHAR ($5B) — fast no-attr path; ESTEX handles CR/LF/scroll - * and cursor itself. Used when g_text_attr = KEEP_EXIST_ATTR. */ -static char _bios_putchar(char ch) __naked -{ - (void)ch; - __asm - ;; c in A. push af stashes it across the RST (which clobbers A). - ;; push ix ; PUTCHAR не меняет IX - push af - ld c, #0x5B ; ESTEX PUTCHAR - rst #0x10 - pop af - ;; pop ix - ret - __endasm; -} - -/* Raw putch: low-level WRCHAR-based output at (pc_col, pc_row) using - * the given attribute byte (caller has already verified that the high - * byte of g_text_attr is zero — this function takes only the low byte). - * Updates pc_col / pc_row per pc_raw_mode: - * - * pc_raw_mode == 0: BS/TAB/LF/CR are INTERPRETED: - * 0x08 BS → pc_col-- (if not already 0) - * 0x09 TAB → pc_col rounded up to next multiple of 8 (capped 80) - * 0x0A LF → pc_row++ (capped at 32; no glyph) - * 0x0D CR → pc_col = 0 - * other → WRCHAR + pc_col++ - * - * pc_raw_mode == 1: ALL characters print as glyphs via WRCHAR - * + pc_col++ (including 0x08, 0x09, 0x0A, 0x0D — they render as - * their CP437 glyphs). - * - * WRCHAR itself is suppressed when pc_col ≥ 80 or pc_row ≥ 32 (off- - * screen) — coordinates [0..79] × [0..31] only. - * - * Does NOT call CURSOR / LOCATE — caller is expected to fetch cursor - * once before a sequence of _raw_putch calls and write it back once - * after, so we pay the BIOS overhead per OPERATION instead of per CHAR. */ -/* Mode-0 worker: interprets BS/TAB/LF/CR, outputs other chars as glyphs. */ -static void _putch_wrchar(char ch, uint8_t attr) __naked -{ - (void)ch; (void)attr; - __asm - ;; __sdcccall(1): ch in A, attr in L. - ;; Dispatch on control chars while A still holds ch (cp does not - ;; modify A). B/C only get loaded on the output path so the - ;; ctrl-char paths are cheaper. - - ;; ld c, a - ;; ld a, (_pc_ch) ; A = ch (`ld a,(nn)` does not touch flags) - ;; jr nz, _rp0_pri - - ;; ld a, c - cp #0x08 - jr z, _rp0_bs - cp #0x09 - jr z, _rp0_tab - cp #0x0A - jr z, _rp0_lf - cp #0x0D - jr z, _rp0_cr - ;; Anything else (printable or unrecognised ctrl) → glyph. - - _rp0_pri: - - ld c, a ; C = ch (save before A is clobbered) - ld a, (_pc_place + 1) - cp #32 - ret nc ; off-screen bottom — silently skip - ld d, a ; D = row (ESTEX WRCHAR convention) - ld a, (_pc_place) - cp #80 - ret nc ; off-screen right — silently skip - ld e, a ; E = col - inc a - ld (_pc_place), a ; pc_col++ - - _rp0_wr: - ld b, l ; B = attr - ld a, c ; A = ch - push ix - ld c, #0x58 ; ESTEX WRCHAR - rst #0x10 - pop ix - ret - - _rp0_bs: - ld a, (_pc_place) - or a, a - ret z ; already at col 0 — no change - dec a - ld (_pc_place), a - ld e, a ; E = col - ld a, (_pc_place + 1) - ld d, a ; D = row (ESTEX WRCHAR convention) - ld c, #0x20 - jr _rp0_wr - - _rp0_tab: - ld a, (_pc_place) - or #0x07 ; floor to mult of 8 - inc a ; → next mult of 8 - cp #81 ; сравнить A с 81 (0x51) - jr c, _rp0_tab_skip ; если A < 81 (т.е. A ≤ 80), пропустить загрузку - ld a, #80 ; иначе A > 80 → установить A = 80 - _rp0_tab_skip: - ld (_pc_place), a - ret - - _rp0_lf: - ld a, (_pc_place + 1) - cp #32 - ret nc ; already at bottom edge - inc a - ld (_pc_place + 1), a - ret - - _rp0_cr: - xor a, a - ld (_pc_place), a - ret - __endasm; -} - -/* Mode-1 worker: every byte goes through WRCHAR as a glyph. */ -static void _putch_wrchar_raw(char ch, uint8_t attr) __naked -{ - (void)ch; (void)attr; - __asm - ;; __sdcccall(1): ch in A, attr in L. - ld c, a ; C = ch (save) - - ld a, (_pc_place + 1) - cp #32 - ret nc ; off-screen bottom — silently skip - ld d, a ; D = row - ld a, (_pc_place) - cp #80 - ret nc ; off-screen right — silently skip - ld e, a ; E = col - inc a - ld (_pc_place), a ; pc_col++ - - ld b, l ; B = attr - ld a, c ; A = ch - push ix - ld c, #0x58 ; ESTEX WRCHAR - rst #0x10 - pop ix - ret - __endasm; -} - - -/* ---- Public putch / cputs --------------------------------------- * - * - * KEEP_EXIST_ATTR (high byte != 0) → fast PUTCHAR/PCHARS through - * ESTEX, which manages its own cursor. - * - * Otherwise → fetch cursor ONCE via CURSOR ($53), run one or many - * _raw_putch calls, write cursor back ONCE via LOCATE ($52). This - * folds the per-char CURSOR/LOCATE pair from the old design into a - * single pair per operation. */ - - -char putch(char ch) __naked -{ - (void)ch; - __asm - ;; A = ch on entry; char return → A. - ld (_pc_ch), a ; stash c (for both return and re-load) - - ld a, (_g_text_attr) ; A = low byte = attr - ld l, a ; L = attr - - ld a, (_pc_raw_mode) - or a, a ; Z = (mode == 0) - ld a, (_pc_ch) ; A = ch (`ld a,(nn)` does not touch flags) - jr nz, _putch_use_raw - call __putch_wrchar - jr _putch_after_raw - _putch_use_raw: - call __putch_wrchar_raw - _putch_after_raw: - call __set_cursor - ld a, (_pc_ch) ; return value - ret - - __endasm; -} - - -char cputs(const char *s) __naked -{ - (void)s; - __asm - ;; HL = s on entry; char return → A. - - ;; NULL-check: cputs(NULL) → return 0 immediately. - ld a, h - or a, l - ret z - - push ix - - call __get_cursor - - ;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0 - ld a, (_g_text_attr + 1) - or a, a - jr nz, _cputs_fast - - ld a, (_pc_raw_mode) - or a, a - jr nz, _cputs_bios - - push hl - pop de - - _cputs_wrloop: - ld a, (_g_text_attr) - ld l, a - ld a, (de) ; загрузить байт - or a ; установить флаг Z, если A == 0 - jr z, _cputs_ex ; завершить подпрограмму (конец строки) - push de - call __putch_wrchar ; вывести символ (A передаётся как аргумент) - pop de - inc de ; перейти к следующему байту - jr _cputs_wrloop ; повторить - - _cputs_bios: - ld a, (_g_text_attr) - ld b, #0xFF - ld d, #0x0 - ld e, a - ld c, #0x8B ; BIOS LP_PRINT_LN5 - rst #0x08 - jr _cputs_ex - _cputs_fast: - ld c, #0x5C ; ESTEX PCHARS - rst #0x10 - _cputs_ex: - call __set_cursor - pop ix ; restore callers IX - xor a, a ; return 0 - ret - __endasm; -} - -void clrscr(void) __naked -{ - __asm - ld a, #0x0F - jp _clrscr_attr - __endasm; -} - -void clrscr_attr(uint8_t attr) __naked -{ - (void)attr; - __asm - push ix - ;; SDCC __sdcccall(1): uint8_t 1st arg is in A. - ld b, a ; B = attribute (mode-fill colour) - ld de, #0x0000 ; top-left - ld hl, #0x2050 ; H=32 rows, L=80 cols - ld a, #0x20 ; space fill - ld c, #0x56 ; ESTEX CLEAR - rst #0x10 - pop ix - ret - __endasm; -} - -void gotoxy(uint8_t x, uint8_t y) __naked -{ - (void)x; (void)y; - __asm - ;; __sdcccall(1) 2 uint8 args: x in A, y in L. - ;; ESTEX LOCATE ($52) wants: D = row, E = col. - ;; push ix - ld d, l ; D = row (y) - ld e, a ; E = col (x) - ld (_pc_place), de - ld c, #0x84 ; BIOS SetCursor - rst #0x08 - ;; ld c, #0x52 - ;; rst #0x10 - ;; pop ix - ret - __endasm; -} - -uint8_t wherex(void) __naked -{ - __asm - ;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE. - ;; push ix - ld c, #0x8e ; BIOS GetCursor - rst #0x08 - ;; ld c, #0x53 - ;; rst #0x10 - ;; pop ix - ld a, e - ld (_pc_place), de - ret - __endasm; -} - -uint8_t wherey(void) __naked -{ - __asm - ;; push ix - ld c, #0x8e ; BIOS GetCursor - rst #0x08 - ;; ld c, #0x53 - ;; rst #0x10 - ;; pop ix - ld a, d - ld (_pc_place), de - ret - __endasm; -} - -uint16_t wherexy(void) __naked -{ - __asm - ;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE. - ;; push ix - ld c, #0x8e ; BIOS GetCursor - rst #0x08 - ;; ld c, #0x53 - ;; rst #0x10 - ;; pop ix - ld (_pc_place), de - ret - __endasm; -} - -/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr) - * - * SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr - * packed and pushed on the stack as a single 16-bit value (caller does - * `ld hl, #(attr<<8)|ch; push hl`). Layout after CALL: - * [SP+0..1] = return address - * [SP+2] = ch (low half of pushed pair) - * [SP+3] = attr (high half) - * Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy). - */ -void scroll(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t direction, uint8_t clear) __naked -{ - (void)x; (void)y; (void)w; (void)h; (void)direction; (void)clear; - __asm - pop iy ; return address - ld d, l ; D = row (y) - ld e, a ; E = col (x) - pop hl ; H = heigth(h), L = width(w) - pop bc ; C = direction, B = clear - ld a, b ; A = clear(B) - ld b, c ; B = direction(C) - push ix - ld c, #0x55 ; ESTEX SCROLL - rst #0x10 - pop ix - jp (iy) - __endasm; -} - - - - -/* wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr) - * - * SDCC __sdcccall(1): x in A, y in L (2 uint8 → A, L); ch and attr - * packed and pushed on the stack as a single 16-bit value (caller does - * `ld hl, #(attr<<8)|ch; push hl`). Layout after CALL: - * [SP+0..1] = return address - * [SP+2] = ch (low half of pushed pair) - * [SP+3] = attr (high half) - * Void return → callee-pops the 2 stack-arg bytes via `pop bc` + jp (iy). - */ -void wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr) __naked -{ - (void)x; (void)y; (void)ch; (void)attr; - __asm - pop iy ; return address - pop bc ; C = ch, B = attr - push ix - ld d, l ; D = row (y) - ld e, a ; E = col (x) - ld a, c ; A = ch - ld c, #0x58 ; ESTEX WRCHAR - rst #0x10 - pop ix - jp (iy) - __endasm; -} - -/* rdchar(int x, int y) → (attr << 8) | ch */ -uint16_t rdchar(uint8_t x, uint8_t y) __naked -{ - (void)x; (void)y; - __asm - push ix - ld d, l ; D = row - ld e, a ; E = col - ld c, #0x57 ; ESTEX RDCHAR - rst #0x10 - ;; A = ch, B = attr - ld d, b ; high byte → attr - ld e, a ; low byte → ch - pop ix - ret - __endasm; -} - -/* Public text-mode video API — defined here so it's pulled in with the - * rest of conio. The raw setters/getters live in videomode_raw.c so - * pure graphics programs can pick them up without conio's other - * dependencies. */ -extern uint8_t _videomode_raw_get(void); -extern int _videomode_raw_set(uint8_t mode); - -uint8_t gettextmode(void) -{ - return _videomode_raw_get(); -} - -int settextmode(uint8_t mode) -{ - /* Refuse anything that isn't a known text mode — otherwise a stray - * GFX_MODE_* value could swap the screen out from under text I/O. */ - if (mode != TEXT_MODE_40x32 && mode != TEXT_MODE_80x32) { - errno = EINVAL; - return -1; - } - return _videomode_raw_set(mode); -} - -/* ---- text attribute state ---------------------------------------- - * g_text_attr is owned by conio.c now (Turbo-C-style: stdio putchar/puts - * are fast and attribute-free; only conio's putch/cputs/cprintf apply - * the attribute). Default = 0x0F (bright white on black). - * - * 0x00..0xFF — real attribute (4-bit FG | 3-bit BG | 1-bit blink) - * KEEP_EXIST_ATTR (0xFFFF) — putch/cputs fall back to fast no-attr path */ - -int16_t set_text_attr(int16_t attr) -{ - int16_t prev = g_text_attr.value; - g_text_attr.value = attr; - return prev; -} - -int16_t get_text_attr(void) -{ - return g_text_attr.value; -} - -/* ---- Turbo-C-style palette helpers -------------------------------- - * textcolor / textbackground touch only their nibble; the other nibble - * (and the blink bit) are preserved. textattr replaces the whole byte. */ - -void textcolor(uint8_t fg) -{ - fg = (fg & 0x07); - g_text_attr.value = (g_text_attr.byte.low & 0xF0) | fg; - // g_text_attr.byte.high = 0; -} - -void textbackground(uint8_t bg) -{ - bg = (bg & 0x07) << 4; - g_text_attr.value = (g_text_attr.byte.low & 0x0F) | bg; - // g_text_attr.byte.high = 0; -} - -void textattr(uint8_t attr) -{ - g_text_attr.value = (uint16_t)attr; -} - -/* ---- Solid-C compatibility ---------------------------------------- */ - -/* Direct port I/O. Z80 has 256 IN/OUT ports; we wrap the Z80 IN/OUT - * opcodes with a stable C API. Names match Solid-C / MS-DOS Turbo-C. */ -uint8_t z80_inp(uint8_t port) __naked -{ - (void)port; - __asm - ;; SDCC __sdcccall(1): single uint8_t arg in A; uint8_t return in A. - ld c, a - in a, (c) - ret - __endasm; -} - -void z80_outp(uint8_t port, uint8_t value) __naked -{ - (void)port; (void)value; - __asm - ;; __sdcccall(1): 2 uint8 args → arg1 in A, arg2 in L. - ld c, a ; C = port - out (c), l ; out (port), value - ret - __endasm; -} - -/* cgets — Solid-C / Turbo-C style line input. - * buf[0] = max characters (in) - * buf[1] = actual count (out) - * buf[2..] = chars + NUL - * Returns &buf[2]. */ -char *cgets(char *buf) -{ - uint8_t maxlen = (uint8_t)buf[0]; - uint8_t n = 0; - while (n < maxlen) { - int ch = getche(); - if (ch == '\n' || ch == '\r') { - putch('\r'); putch('\n'); - break; - } - if (ch == 8) { /* backspace */ - if (n > 0) { n--; } - continue; - } - buf[2 + n] = (char)ch; - n++; - } - buf[1] = (char)n; - buf[2 + n] = 0; - return &buf[2]; -} diff --git a/libc/conio/cprintf.c b/libc/conio/cprintf.c index 52a63c8..fc43484 100644 --- a/libc/conio/cprintf.c +++ b/libc/conio/cprintf.c @@ -1,12 +1,13 @@ /* - * cprintf — printf for the conio output set. Formats into a static - * buffer with vsprintf (from SDCC's stdlib), then emits via cputs which - * applies the current text attribute per character. + * cprintf — printf для conio-набора вывода. Форматирует в статический + * буфер через vsprintf (из stdlib SDCC), затем выводит cputs'ом, + * который применяет текущий текстовый атрибут посимвольно. * - * No '\n' to CR LF translation — Turbo-C convention: callers write - * "\r\n" explicitly in the format string for line breaks. + * '\n' в CR LF НЕ транслируется — конвенция Turbo-C: перевод строки + * пишется в format-строке явно как "\r\n". * - * Not reentrant (single static buffer) but Z80 single-threaded is fine. + * Не реентерабельно (один статический буфер), но на однопоточном Z80 + * это не проблема. */ #include diff --git a/libc/conio/cputs.c b/libc/conio/cputs.c new file mode 100644 index 0000000..0900968 --- /dev/null +++ b/libc/conio/cputs.c @@ -0,0 +1,72 @@ +/* + * cputs — вывод ASCIIZ-строки с текущим атрибутом (Turbo-C conio). + * + * Курсор BIOS читается один раз до строки и записывается один раз + * после. Три пути: + * g_text_attr == KEEP_EXIST_ATTR → быстрый ESTEX PCHARS ($5C); + * pc_raw_mode == 1 → BIOS LP_PRINT_LN5 (глифы с атрибутом); + * иначе → цикл _putch_wrchar (интерпретация + * BS/TAB/LF/CR). + * Трансляции '\n' → CR LF нет — вызывающий пишет "\r\n" сам. + * cputs(NULL) — no-op. Возвращает 0. + */ + +#include "_conio.h" + +char cputs(const char *s) __naked +{ + (void)s; + __asm + ;; HL = s на входе; возврат char → A. + + ;; NULL-check: cputs(NULL) → сразу вернуть 0. + ld a, h + or a, l + ret z + + push ix + + call __get_cursor + + ;; KEEP_EXIST_ATTR? старший байт g_text_attr != 0 + ld a, (_g_text_attr + 1) + or a, a + jr nz, _cputs_fast + + ld a, (_pc_raw_mode) + or a, a + jr nz, _cputs_bios + + push hl + pop de + + _cputs_wrloop: + ld a, (_g_text_attr) + ld l, a + ld a, (de) ; загрузить байт + or a ; установить флаг Z, если A == 0 + jr z, _cputs_ex ; завершить подпрограмму (конец строки) + push de + call __putch_wrchar ; вывести символ (A передаётся как аргумент) + pop de + inc de ; перейти к следующему байту + jr _cputs_wrloop ; повторить + + _cputs_bios: + ld a, (_g_text_attr) + ld b, #0xFF + ld d, #0x0 + ld e, a + ld c, #0x8B ; BIOS LP_PRINT_LN5 + rst #0x08 + jr _cputs_ex + _cputs_fast: + ld c, #0x5C ; ESTEX PCHARS + rst #0x10 + _cputs_ex: + call __set_cursor + pop ix ; восстановить IX вызывающего + xor a, a ; вернуть 0 + ret + __endasm; +} diff --git a/libc/conio/get_putch_raw_mode.c b/libc/conio/get_putch_raw_mode.c new file mode 100644 index 0000000..32bbbdd --- /dev/null +++ b/libc/conio/get_putch_raw_mode.c @@ -0,0 +1,11 @@ +/* + * get_putch_raw_mode — текущий режим обработки управляющих символов + * putch/cputs (см. set_putch_raw_mode). + */ + +#include "_conio.h" + +uint8_t get_putch_raw_mode(void) +{ + return pc_raw_mode; +} diff --git a/libc/conio/get_text_attr.c b/libc/conio/get_text_attr.c new file mode 100644 index 0000000..6f26204 --- /dev/null +++ b/libc/conio/get_text_attr.c @@ -0,0 +1,10 @@ +/* + * get_text_attr — текущий 16-битный атрибут conio (см. set_text_attr). + */ + +#include "_conio.h" + +int16_t get_text_attr(void) +{ + return g_text_attr.value; +} diff --git a/libc/conio/getch.c b/libc/conio/getch.c new file mode 100644 index 0000000..e271b45 --- /dev/null +++ b/libc/conio/getch.c @@ -0,0 +1,18 @@ +/* + * getch — блокирующее чтение клавиши БЕЗ эха на экран. + * ESTEX WAITKEY ($30); ASCII возвращается в A. + */ + +#include "_conio.h" + +char getch(void) __naked +{ + __asm + push ix + ld c, #0x30 ; ESTEX WAITKEY (без эха) + rst #0x10 + pop ix + ;; ESTEX возвращает ASCII в E (и копию в A) + ret + __endasm; +} diff --git a/libc/conio/getche.c b/libc/conio/getche.c new file mode 100644 index 0000000..22373c4 --- /dev/null +++ b/libc/conio/getche.c @@ -0,0 +1,18 @@ +/* + * getche — блокирующее чтение клавиши С эхом на экран. + * ESTEX ECHOKEY ($32); ASCII возвращается в A. + */ + +#include "_conio.h" + +char getche(void) __naked +{ + __asm + push ix + ld c, #0x32 ; ESTEX ECHOKEY (эхо на консоль) + rst #0x10 + pop ix + ;; ESTEX возвращает ASCII в E (и копию в A) + ret + __endasm; +} diff --git a/libc/conio/getkey.c b/libc/conio/getkey.c new file mode 100644 index 0000000..dffbdc9 --- /dev/null +++ b/libc/conio/getkey.c @@ -0,0 +1,26 @@ +/* + * getkey — как getch(), но возвращает И ASCII, И позиционный скан-код, + * чтобы различать расширенные клавиши (стрелки, F1..F12, PgUp/PgDn, + * Home/End, Ins/Del — у них ASCII == 0 из ESTEX) и обычные символы. + * + * возврат = (scan << 8) | ascii + * + * Обычные клавиши: ascii — символ, scan — позиционный код (бит 7 + * взведён при Ctrl/Alt/Shift). + * Расширенные: ascii == 0, клавишу определяет scan (см. KEY_* в + * ). + */ + +#include "_conio.h" + +uint16_t getkey(void) __naked +{ + __asm + push ix + ld c, #0x30 ; ESTEX WAITKEY: A=ASCII, D=scan, E=ASCII + rst #0x10 + pop ix + ld e, a ; E = ASCII (защитно: канонична копия в A) + ret ; __sdcccall(1) возвращает uint16_t в DE + __endasm; +} diff --git a/libc/conio/gettextmode.c b/libc/conio/gettextmode.c new file mode 100644 index 0000000..bca57c2 --- /dev/null +++ b/libc/conio/gettextmode.c @@ -0,0 +1,15 @@ +/* + * gettextmode — текущий видеорежим (обёртка _videomode_raw_get из + * video/videomode_raw.c: сырые геттер/сеттер живут отдельно, чтобы + * чисто графические программы не тянули conio). + */ + +#include +#include + +extern uint8_t _videomode_raw_get(void); + +uint8_t gettextmode(void) +{ + return _videomode_raw_get(); +} diff --git a/libc/conio/gotoxy.c b/libc/conio/gotoxy.c new file mode 100644 index 0000000..0e025a0 --- /dev/null +++ b/libc/conio/gotoxy.c @@ -0,0 +1,20 @@ +/* + * gotoxy — установить курсор в (x=col, y=row), 0-based. + * Обновляет pc_place и ставит курсор BIOS SetCursor (rst 8, $84). + */ + +#include "_conio.h" + +void gotoxy(uint8_t x, uint8_t y) __naked +{ + (void)x; (void)y; + __asm + ;; __sdcccall(1), 2 аргумента uint8: x в A, y в L. + ld d, l ; D = row (y) + ld e, a ; E = col (x) + ld (_pc_place), de + ld c, #0x84 ; BIOS SetCursor + rst #0x08 + ret + __endasm; +} diff --git a/libc/conio/kbhit.c b/libc/conio/kbhit.c new file mode 100644 index 0000000..09607c1 --- /dev/null +++ b/libc/conio/kbhit.c @@ -0,0 +1,23 @@ +/* + * kbhit — 1, если в буфере клавиатуры есть символ, иначе 0. + * ESTEX CTRLKEY ($33) подглядывает, не забирая символ из буфера. + */ + +#include "_conio.h" + +// TODO - проверить - ф-ии 30h-33h (kbhit/getch/getche/getkey) +// не должны менять IX и им можно не делать push ix / pop ix +char kbhit(void) __naked +{ + __asm + push ix + ld c, #0x33 ; ESTEX CTRLKEY — подглядывает без изъятия + rst #0x10 + pop ix + ;; A=0 → клавиши нет; ненулевой → есть. + or a, a + ret z + ld a, #0x01 + ret + __endasm; +} diff --git a/libc/conio/putch.c b/libc/conio/putch.c new file mode 100644 index 0000000..0789255 --- /dev/null +++ b/libc/conio/putch.c @@ -0,0 +1,41 @@ +/* + * putch — вывод символа с текущим атрибутом (Turbo-C conio). + * + * Применяет g_text_attr; при pc_raw_mode == 1 управляющие символы + * печатаются как глифы (воркер _putch_wrchar_raw), иначе + * интерпретируются (_putch_wrchar). После вывода записывает курсор + * BIOS один раз (_set_cursor). Трансляции '\n' → CR LF нет — + * Turbo-C-конвенция, вызывающий пишет "\r\n" сам. + * Возвращает выведенный символ. + */ + +#include "_conio.h" + +/* Стэш символа на время вызовов воркеров (только для putch). */ +static uint8_t pc_ch; + +char putch(char ch) __naked +{ + (void)ch; + __asm + ;; A = ch на входе; возврат char → A. + ld (_pc_ch), a ; спрятать ch (для возврата и перезагрузки) + + ld a, (_g_text_attr) ; A = младший байт = attr + ld l, a ; L = attr + + ld a, (_pc_raw_mode) + or a, a ; Z = (mode == 0) + ld a, (_pc_ch) ; A = ch (`ld a,(nn)` не трогает флаги) + jr nz, _putch_use_raw + call __putch_wrchar + jr _putch_after_raw + _putch_use_raw: + call __putch_wrchar_raw + _putch_after_raw: + call __set_cursor + ld a, (_pc_ch) ; возвращаемое значение + ret + + __endasm; +} diff --git a/libc/conio/rdchar.c b/libc/conio/rdchar.c new file mode 100644 index 0000000..f82aa1b --- /dev/null +++ b/libc/conio/rdchar.c @@ -0,0 +1,23 @@ +/* + * rdchar — прочитать символ и атрибут из позиции (x, y): + * возврат (attr << 8) | ch. ESTEX RDCHAR ($57). + */ + +#include "_conio.h" + +uint16_t rdchar(uint8_t x, uint8_t y) __naked +{ + (void)x; (void)y; + __asm + push ix + ld d, l ; D = row + ld e, a ; E = col + ld c, #0x57 ; ESTEX RDCHAR + rst #0x10 + ;; A = ch, B = attr + ld d, b ; старший байт → attr + ld e, a ; младший байт → ch + pop ix + ret + __endasm; +} diff --git a/libc/conio/scroll.c b/libc/conio/scroll.c new file mode 100644 index 0000000..0e6b33c --- /dev/null +++ b/libc/conio/scroll.c @@ -0,0 +1,30 @@ +/* + * scroll — прокрутка окна (x, y, w, h) на 1 позицию в направлении + * direction, с очисткой освободившейся строки/колонки атрибутом clear. + * ESTEX SCROLL ($55). + * + * SDCC __sdcccall(1): x в A, y в L; w,h и direction,clear запушены + * парами на стек. Void-возврат → callee снимает стековые аргументы + * (pop до RST) и уходит через jp (iy). + */ + +#include "_conio.h" + +void scroll(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t direction, uint8_t clear) __naked +{ + (void)x; (void)y; (void)w; (void)h; (void)direction; (void)clear; + __asm + pop iy ; адрес возврата + ld d, l ; D = row (y) + ld e, a ; E = col (x) + pop hl ; H = высота (h), L = ширина (w) + pop bc ; C = direction, B = clear + ld a, b ; A = clear(B) + ld b, c ; B = direction(C) + push ix + ld c, #0x55 ; ESTEX SCROLL + rst #0x10 + pop ix + jp (iy) + __endasm; +} diff --git a/libc/conio/set_putch_raw_mode.c b/libc/conio/set_putch_raw_mode.c new file mode 100644 index 0000000..6dd90ce --- /dev/null +++ b/libc/conio/set_putch_raw_mode.c @@ -0,0 +1,12 @@ +/* + * set_putch_raw_mode — режим обработки управляющих символов в + * putch/cputs: 0 — BS/TAB/LF/CR интерпретируются, 1 — все байты + * печатаются как глифы CP437 (см. pc_raw_mode). + */ + +#include "_conio.h" + +void set_putch_raw_mode(uint8_t mode) +{ + pc_raw_mode = mode; +} diff --git a/libc/conio/set_text_attr.c b/libc/conio/set_text_attr.c new file mode 100644 index 0000000..a5fc2b5 --- /dev/null +++ b/libc/conio/set_text_attr.c @@ -0,0 +1,14 @@ +/* + * set_text_attr — установить полный 16-битный атрибут conio + * (0x00..0xFF — реальный атрибут; KEEP_EXIST_ATTR — быстрый путь без + * атрибута). Возвращает прежнее значение. + */ + +#include "_conio.h" + +int16_t set_text_attr(int16_t attr) +{ + int16_t prev = g_text_attr.value; + g_text_attr.value = attr; + return prev; +} diff --git a/libc/conio/settextmode.c b/libc/conio/settextmode.c new file mode 100644 index 0000000..b1e9812 --- /dev/null +++ b/libc/conio/settextmode.c @@ -0,0 +1,22 @@ +/* + * settextmode — установить ТЕКСТОВЫЙ видеорежим (TEXT_MODE_40x32 / + * TEXT_MODE_80x32). Любое другое значение отвергается с EINVAL — + * иначе случайный GFX_MODE_* мог бы выдернуть экран из-под текстового + * I/O. Возвращает 0 или -1 + errno. + */ + +#include +#include +#include + +extern int _videomode_raw_set(uint8_t mode); + +int settextmode(uint8_t mode) +{ + /* Отвергаем всё, что не известный текстовый режим. */ + if (mode != TEXT_MODE_40x32 && mode != TEXT_MODE_80x32) { + errno = EINVAL; + return -1; + } + return _videomode_raw_set(mode); +} diff --git a/libc/conio/text_pal_get.c b/libc/conio/text_pal_get.c new file mode 100644 index 0000000..997f943 --- /dev/null +++ b/libc/conio/text_pal_get.c @@ -0,0 +1,12 @@ +/* + * text_pal_get — прочитать блок записей текстовой палитры; + * план 0..3 транслируется в палитровую страницу BIOS 4..7. + */ + +#include +#include + +void text_pal_get(uint8_t plane, uint8_t start, uint8_t count, uint8_t *data) +{ + pal_get((uint8_t)(plane + 4u), start, count, data); +} diff --git a/libc/conio/text_pal_get_color.c b/libc/conio/text_pal_get_color.c new file mode 100644 index 0000000..a5cbfe0 --- /dev/null +++ b/libc/conio/text_pal_get_color.c @@ -0,0 +1,13 @@ +/* + * text_pal_get_color — прочитать одну запись текстовой палитры в R/G/B; + * план 0..3 транслируется в палитровую страницу BIOS 4..7. + */ + +#include +#include + +void text_pal_get_color(uint8_t plane, uint8_t attr, + uint8_t *r, uint8_t *g, uint8_t *b) +{ + pal_get_color((uint8_t)(plane + 4u), attr, r, g, b); +} diff --git a/libc/conio/text_pal_load.c b/libc/conio/text_pal_load.c new file mode 100644 index 0000000..b0e0e4d --- /dev/null +++ b/libc/conio/text_pal_load.c @@ -0,0 +1,14 @@ +/* + * text_pal_load — загрузить блок записей текстовой палитры. + * Текстовый «план» 0..3 (paper/ink/blink-paper/blink-ink) транслируется + * в палитровую страницу BIOS 4..7 (см. ). + */ + +#include +#include + +void text_pal_load(uint8_t plane, uint8_t start, uint8_t count, + const uint8_t *data) +{ + pal_load((uint8_t)(plane + 4u), start, count, data); +} diff --git a/libc/conio/text_pal_reset.c b/libc/conio/text_pal_reset.c new file mode 100644 index 0000000..16aa0d6 --- /dev/null +++ b/libc/conio/text_pal_reset.c @@ -0,0 +1,12 @@ +/* + * text_pal_reset — восстановить системную текстовую палитру + * (BIOS $A6, type = PAL_CGA, страницы 4..7). + */ + +#include +#include + +void text_pal_reset(void) +{ + pal_reset(PAL_CGA); +} diff --git a/libc/conio/text_pal_set_color.c b/libc/conio/text_pal_set_color.c new file mode 100644 index 0000000..29f1840 --- /dev/null +++ b/libc/conio/text_pal_set_color.c @@ -0,0 +1,13 @@ +/* + * text_pal_set_color — одна запись текстовой палитры из RGB; + * план 0..3 транслируется в палитровую страницу BIOS 4..7. + */ + +#include +#include + +void text_pal_set_color(uint8_t plane, uint8_t attr, + uint8_t r, uint8_t g, uint8_t b) +{ + pal_set_color((uint8_t)(plane + 4u), attr, r, g, b); +} diff --git a/libc/conio/text_palette.c b/libc/conio/text_palette.c deleted file mode 100644 index 1582efd..0000000 --- a/libc/conio/text_palette.c +++ /dev/null @@ -1,42 +0,0 @@ -/* - * text_palette.c — text-mode palette wrappers. - * - * Thin layer over libc/video/palette.c. Translates the text "plane - * number" 0..3 (paper/ink/blink-paper/blink-ink) into the underlying - * BIOS palette page 4..7 used by $A4. - * - * For an introduction to the four-plane text colour model see - * (top-of-file doc-block). - */ - -#include -#include -#include - -void text_pal_load(uint8_t plane, uint8_t start, uint8_t count, - const uint8_t *data) -{ - pal_load((uint8_t)(plane + 4u), start, count, data); -} - -void text_pal_set_color(uint8_t plane, uint8_t attr, - uint8_t r, uint8_t g, uint8_t b) -{ - pal_set_color((uint8_t)(plane + 4u), attr, r, g, b); -} - -void text_pal_get(uint8_t plane, uint8_t start, uint8_t count, uint8_t *data) -{ - pal_get((uint8_t)(plane + 4u), start, count, data); -} - -void text_pal_get_color(uint8_t plane, uint8_t attr, - uint8_t *r, uint8_t *g, uint8_t *b) -{ - pal_get_color((uint8_t)(plane + 4u), attr, r, g, b); -} - -void text_pal_reset(void) -{ - pal_reset(PAL_CGA); -} diff --git a/libc/conio/textattr.c b/libc/conio/textattr.c new file mode 100644 index 0000000..6e6bd88 --- /dev/null +++ b/libc/conio/textattr.c @@ -0,0 +1,11 @@ +/* + * textattr — заменить атрибут целиком одним байтом (fg | bg | blink). + * Turbo-C-стиль. Сбрасывает KEEP_EXIST_ATTR (старший байт = 0). + */ + +#include "_conio.h" + +void textattr(uint8_t attr) +{ + g_text_attr.value = (uint16_t)attr; +} diff --git a/libc/conio/textbackground.c b/libc/conio/textbackground.c new file mode 100644 index 0000000..842c720 --- /dev/null +++ b/libc/conio/textbackground.c @@ -0,0 +1,13 @@ +/* + * textbackground — установить цвет фона (биты 4..6 атрибута), + * цвет символов и бит blink сохраняются. Turbo-C-стиль. Попутно + * сбрасывает KEEP_EXIST_ATTR (старший байт становится 0). + */ + +#include "_conio.h" + +void textbackground(uint8_t bg) +{ + bg = (bg & 0x07) << 4; + g_text_attr.value = (g_text_attr.byte.low & 0x0F) | bg; +} diff --git a/libc/conio/textcolor.c b/libc/conio/textcolor.c new file mode 100644 index 0000000..e558097 --- /dev/null +++ b/libc/conio/textcolor.c @@ -0,0 +1,13 @@ +/* + * textcolor — установить цвет символов (нижний нибл атрибута), + * фон и бит blink сохраняются. Turbo-C-стиль. Попутно сбрасывает + * KEEP_EXIST_ATTR (старший байт становится 0). + */ + +#include "_conio.h" + +void textcolor(uint8_t fg) +{ + fg = (fg & 0x07); + g_text_attr.value = (g_text_attr.byte.low & 0xF0) | fg; +} diff --git a/libc/conio/wherex.c b/libc/conio/wherex.c new file mode 100644 index 0000000..21603e4 --- /dev/null +++ b/libc/conio/wherex.c @@ -0,0 +1,17 @@ +/* + * wherex — текущая колонка курсора (0..79). + * Читает курсор BIOS GetCursor (rst 8, $8E), попутно обновляя pc_place. + */ + +#include "_conio.h" + +uint8_t wherex(void) __naked +{ + __asm + ld c, #0x8e ; BIOS GetCursor: D = row, E = col + rst #0x08 + ld a, e + ld (_pc_place), de + ret + __endasm; +} diff --git a/libc/conio/wherexy.c b/libc/conio/wherexy.c new file mode 100644 index 0000000..3ad7a4f --- /dev/null +++ b/libc/conio/wherexy.c @@ -0,0 +1,17 @@ +/* + * wherexy — обе координаты курсора одним вызовом: + * (row << 8) | col в DE. Читает курсор BIOS GetCursor (rst 8, $8E), + * попутно обновляя pc_place. + */ + +#include "_conio.h" + +uint16_t wherexy(void) __naked +{ + __asm + ld c, #0x8e ; BIOS GetCursor: D = row, E = col + rst #0x08 + ld (_pc_place), de + ret ; __sdcccall(1): uint16_t в DE + __endasm; +} diff --git a/libc/conio/wherey.c b/libc/conio/wherey.c new file mode 100644 index 0000000..77c12d5 --- /dev/null +++ b/libc/conio/wherey.c @@ -0,0 +1,17 @@ +/* + * wherey — текущая строка курсора (0..31). + * Читает курсор BIOS GetCursor (rst 8, $8E), попутно обновляя pc_place. + */ + +#include "_conio.h" + +uint8_t wherey(void) __naked +{ + __asm + ld c, #0x8e ; BIOS GetCursor: D = row, E = col + rst #0x08 + ld a, d + ld (_pc_place), de + ret + __endasm; +} diff --git a/libc/conio/wrchar.c b/libc/conio/wrchar.c new file mode 100644 index 0000000..4b0564a --- /dev/null +++ b/libc/conio/wrchar.c @@ -0,0 +1,32 @@ +/* + * wrchar — записать символ ch с атрибутом attr в позицию (x, y), не + * трогая курсор. ESTEX WRCHAR ($58). + * + * SDCC __sdcccall(1): x в A, y в L (2 uint8 → A, L); ch и attr + * упакованы и запушены на стек одной 16-битной парой (вызывающий + * делает `ld hl, #(attr<<8)|ch; push hl`). Раскладка после CALL: + * [SP+0..1] = адрес возврата + * [SP+2] = ch (младшая половина пары) + * [SP+3] = attr (старшая половина) + * Void-возврат → callee снимает 2 байта стековых аргументов через + * `pop bc` + jp (iy). + */ + +#include "_conio.h" + +void wrchar(uint8_t x, uint8_t y, char ch, uint8_t attr) __naked +{ + (void)x; (void)y; (void)ch; (void)attr; + __asm + pop iy ; адрес возврата + pop bc ; C = ch, B = attr + push ix + ld d, l ; D = row (y) + ld e, a ; E = col (x) + ld a, c ; A = ch + ld c, #0x58 ; ESTEX WRCHAR + rst #0x10 + pop ix + jp (iy) + __endasm; +} diff --git a/libc/conio/z80_inp.c b/libc/conio/z80_inp.c new file mode 100644 index 0000000..af928e7 --- /dev/null +++ b/libc/conio/z80_inp.c @@ -0,0 +1,17 @@ +/* + * z80_inp — прочитать байт из порта Z80 (опкод IN). Обёртка с + * стабильным C API; имя как в Solid-C / MS-DOS Turbo-C. + */ + +#include "_conio.h" + +uint8_t z80_inp(uint8_t port) __naked +{ + (void)port; + __asm + ;; SDCC __sdcccall(1): единственный uint8_t аргумент в A; возврат в A. + ld c, a + in a, (c) + ret + __endasm; +} diff --git a/libc/conio/z80_outp.c b/libc/conio/z80_outp.c new file mode 100644 index 0000000..aa81cf8 --- /dev/null +++ b/libc/conio/z80_outp.c @@ -0,0 +1,17 @@ +/* + * z80_outp — записать байт value в порт Z80 (опкод OUT). Обёртка с + * стабильным C API; имя как в Solid-C / MS-DOS Turbo-C. + */ + +#include "_conio.h" + +void z80_outp(uint8_t port, uint8_t value) __naked +{ + (void)port; (void)value; + __asm + ;; __sdcccall(1): 2 аргумента uint8 → arg1 в A, arg2 в L. + ld c, a ; C = port + out (c), l ; out (port), value + ret + __endasm; +} diff --git a/libc/env/env.c b/libc/env/env.c deleted file mode 100644 index 5c1b18c..0000000 --- a/libc/env/env.c +++ /dev/null @@ -1,93 +0,0 @@ -/* - * getenv / putenv via ESTEX ENVIRON ($46). - * - * ESTEX $46 subfn 1 (getenv): - * in: HL = name (ASCIIZ), DE = output buffer (caller-owned) - * out: CF=0 + A != 0 → variable found, value written into [DE..end-1] - * CF=0 + A == 0 → variable not present (note: this is the - * opposite of what DiskSyscalls.txt v1.6 docs - * claim — verified empirically and against - * solid-c's IO.ASM:763-766 implementation) - * CF=1 → error, A = code - * DE on exit points at one past the last byte written - * - * ESTEX $46 subfn 2 (putenv): - * in: HL = "NAME=value" (NUL-terminated) - * out: CF=1 / A = error code on failure - * - * We hand back a pointer into a private 128-byte buffer for getenv(). - * Caller must copy the bytes before the next getenv() call if they - * need to outlive it. - */ - -#include -#include - -static char env_buf[128]; - -char *getenv(const char *name) __naked -{ - (void)name; - __asm - push ix - ;; HL = name on entry; we need to also load DE = env_buf. - ld de, #_env_buf - ld bc, #0x0146 ; ESTEX ENVIRON; subfn: getenv - rst #0x10 - pop ix - jr c, _getenv_err - or a, a - jr Z, _getenv_miss - ld de, #_env_buf - ret - _getenv_err: - call __errno_set - _getenv_miss: - ld de, #0 - ret - __endasm; -} - -int putenv(const char *namevalue) __naked -{ - (void)namevalue; - __asm - push ix - ld bc, #0x0246 ; ESTEX ENVIRON; subfn: setenv - rst #0x10 - pop ix - jr c, _putenv_err - ld de, #0 - ret - _putenv_err: - call __errno_set - ld de, #-1 - ret - __endasm; -} - -/* ESTEX $46 subfn 0 (sysenv): - * in: HL = caller-owned buffer - * out: caller's buffer is filled with one NUL-terminated "NAME=value" - * per env var, then a trailing extra NUL marks the end: - * "PATH=...\0SOLID=H\0\0" - * return: buf on success, -1 on error (errno set). - * Buffer must be large enough for the whole environment. - */ -char *sysenv(char *buf) __naked -{ - (void)buf; - __asm - push ix - push hl ; stash buffer pointer (= return value) - ld bc, #0x0046 ; ESTEX ENVIRON; subfn 0 = sysenv - rst #0x10 - pop de ; DE = buffer pointer - pop ix - ret nc ; success: DE already has buf - ;; CF=1 → A = ESTEX error code. - call __errno_set - ld de, #-1 - ret - __endasm; -} diff --git a/libc/env/getenv.c b/libc/env/getenv.c new file mode 100644 index 0000000..882def7 --- /dev/null +++ b/libc/env/getenv.c @@ -0,0 +1,41 @@ +/* + * getenv — значение переменной окружения через ESTEX ENVIRON ($46, subfn 1): + * in: HL = имя (ASCIIZ), DE = выходной буфер + * out: CF=0 + A != 0 → найдена, значение записано в [DE..end-1] + * CF=0 + A == 0 → переменной нет (примечание: это ПРОТИВОПОЛОЖНО + * тому, что пишет DiskSyscalls.txt v1.6 — проверено + * эмпирически и по solid-c IO.ASM:763-766) + * CF=1 → ошибка, A = код + * + * Возвращает указатель в приватный 128-байтный буфер — скопируй до + * следующего вызова getenv(), если значение должно пережить его. + * NULL — нет переменной или ошибка (errno). + */ + +#include +#include + +static char env_buf[128]; + +char *getenv(const char *name) __naked +{ + (void)name; + __asm + push ix + ;; HL = имя на входе; дополнительно DE = env_buf. + ld de, #_env_buf + ld bc, #0x0146 ; ESTEX ENVIRON; subfn: getenv + rst #0x10 + pop ix + jr c, _getenv_err + or a, a + jr Z, _getenv_miss + ld de, #_env_buf + ret + _getenv_err: + call __errno_set + _getenv_miss: + ld de, #0 + ret + __endasm; +} diff --git a/libc/env/putenv.c b/libc/env/putenv.c new file mode 100644 index 0000000..3022507 --- /dev/null +++ b/libc/env/putenv.c @@ -0,0 +1,26 @@ +/* + * putenv — установить переменную окружения строкой "NAME=value" (ASCIIZ). + * ESTEX ENVIRON ($46, subfn 2): HL = строка; CF=1 / A = код при ошибке. + * Возвращает 0 или -1 + errno. + */ + +#include +#include + +int putenv(const char *namevalue) __naked +{ + (void)namevalue; + __asm + push ix + ld bc, #0x0246 ; ESTEX ENVIRON; subfn: setenv + rst #0x10 + pop ix + jr c, _putenv_err + ld de, #0 + ret + _putenv_err: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/env/sysenv.c b/libc/env/sysenv.c new file mode 100644 index 0000000..b395f11 --- /dev/null +++ b/libc/env/sysenv.c @@ -0,0 +1,28 @@ +/* + * sysenv — снять всё окружение в буфер вызывающего. + * ESTEX ENVIRON ($46, subfn 0): HL = буфер; на выходе буфер заполнен + * ASCIIZ-строками "NAME=value" подряд, конец — дополнительный NUL: + * "PATH=...\0SOLID=H\0\0" + * Буфер должен вмещать всё окружение. Возвращает buf или -1 + errno. + */ + +#include +#include + +char *sysenv(char *buf) __naked +{ + (void)buf; + __asm + push ix + push hl ; спрятать указатель (= возврат) + ld bc, #0x0046 ; ESTEX ENVIRON; subfn 0 = sysenv + rst #0x10 + pop de ; DE = указатель буфера + pop ix + ret nc ; успех: DE уже держит buf + ;; CF=1 → A = код ошибки ESTEX. + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/errno/_errno_set.c b/libc/errno/_errno_set.c index dee06c3..9899478 100644 --- a/libc/errno/_errno_set.c +++ b/libc/errno/_errno_set.c @@ -1,40 +1,40 @@ /* - * _errno_set — set `errno` from an ESTEX error code (0..255 in A). + * _errno_set — установить `errno` из кода ошибки ESTEX (0..255 в A). * - * Replaces the inline pattern + * Заменяет inline-паттерн * ld (_errno), a * xor a, a - * ld (_errno+1), a ; 7 bytes per error path - * with a single - * call __errno_set ; 3 bytes per error path + * ld (_errno+1), a ; 7 байт на каждый error path + * одним + * call __errno_set ; 3 байта на error path * - * Saves ~4 bytes at every libc error handler that converts an ESTEX - * code into the C-side `errno`. Helper itself is 7 bytes; with 10+ - * error paths in our libc the size win is net-positive. + * Экономит ~4 байта в каждом обработчике ошибок libc, конвертирующем + * код ESTEX в C-шный errno. Сам хелпер — 7 байт; при 10+ error path + * в нашей libc выигрыш по размеру суммарно положительный. * * ABI: - * in: A = ESTEX error code (0..255) - * out: HL = A (zero-extended); errno fully overwritten so a prior - * large value (e.g. errno = -1) can't leak its high byte. - * clobbers: HL, AF flags. Caller must not depend on HL afterwards. + * вход: A = код ошибки ESTEX (0..255) + * выход: errno перезаписан целиком, чтобы прежнее большое значение + * (например errno = -1) не оставило свой старший байт. + * клоббер: A, флаги. HL/BC/DE/IX/IY не трогаются. * - * Defensive 16-bit store — see chat 2026-06-02: if anyone ever assigns - * errno via C (`errno = -1`), the high byte becomes 0xFF, and a partial - * 8-bit write here would leave that 0xFF in place. Always writing the - * full word keeps errno honest regardless of who set it last. + * Защитная 16-битная запись — см. чат 2026-06-02: если кто-то присвоит + * errno из C (`errno = -1`), старший байт станет 0xFF, и частичная + * 8-битная запись здесь оставила бы этот 0xFF на месте. Полное слово + * держит errno честным независимо от того, кто писал последним. */ void _errno_set(unsigned char code) __naked { (void)code; __asm - ;; __sdcccall(1): single uint8_t arg already in A. - ;; Write the two bytes separately so HL/BC/DE/IX/IY remain - ;; untouched. Only A is clobbered: it is the input register, - ;; and ABI does not require preserving it across a void call. - ld (_errno), a ; low byte = code + ;; __sdcccall(1): единственный uint8_t-аргумент уже в A. + ;; Пишем два байта по отдельности, чтобы HL/BC/DE/IX/IY остались + ;; нетронутыми. Клобберится только A: это входной регистр, и + ;; ABI не требует сохранять его через void-вызов. + ld (_errno), a ; младший байт = код xor a, a - ld (_errno+1), a ; high byte = 0 + ld (_errno+1), a ; старший байт = 0 ret __endasm; } diff --git a/libc/errno/perror.c b/libc/errno/perror.c new file mode 100644 index 0000000..8252d59 --- /dev/null +++ b/libc/errno/perror.c @@ -0,0 +1,18 @@ +/* + * perror — напечатать "prefix: \r\n" в stderr. + * Пустой/NULL prefix — печатается только текст ошибки. + */ + +#include +#include +#include + +void perror(const char *prefix) +{ + if (prefix && *prefix) { + fputs(prefix, stderr); + fputs(": ", stderr); + } + fputs(strerror(errno), stderr); + fputs("\r\n", stderr); +} diff --git a/libc/errno/errno.c b/libc/errno/strerror.c similarity index 58% rename from libc/errno/errno.c rename to libc/errno/strerror.c index 399683a..a443d37 100644 --- a/libc/errno/errno.c +++ b/libc/errno/strerror.c @@ -1,24 +1,20 @@ /* - * errno.c — strerror / perror over the SDCC-provided `errno` global. + * strerror — текст ошибки по коду ESTEX (0..32); вне диапазона — + * "Unknown error". Таблица повторяет solid-c IO.ASM (английские + * формулировки сохранены для grep-ability). Таблица и функция + * неразделимы — живут одним модулем. * - * The message table mirrors the one in solid-c's IO.ASM (we kept the - * English wording for grep-ability). ESTEX returns codes 0..32 in the - * meaningful range; anything beyond gets "Unknown error". - * - * Note: we deliberately do NOT define `_errno` here — SDCC's - * z80.lib/errno.rel provides it (a single int in _DATA), and our libc - * wrappers (read.c, open.c, etc.) just assign to `errno`. This - * removes the "multiple definition of _errno" link warning. + * Примечание: `_errno` здесь сознательно НЕ определяется — его даёт + * SDCC z80.lib/errno.rel (int в _DATA), наши обёртки просто пишут в + * `errno`. Это убирает варнинг "multiple definition of _errno". */ #include -#include -#include /* - * Stored verbatim — pointers in the lookup table cost 2 bytes each plus - * the message bytes themselves. Sentinel "" entries pad out gaps so - * indexing stays direct. + * Хранится как есть — указатель в таблице стоит 2 байта плюс сами + * байты сообщения. Заглушки "RESERVED" затыкают дыры, чтобы + * индексирование оставалось прямым. */ static const char *const messages[] = { /* 0 */ "No error", @@ -65,13 +61,3 @@ const char *strerror(int err) } return messages[err]; } - -void perror(const char *prefix) -{ - if (prefix && *prefix) { - fputs(prefix, stderr); - fputs(": ", stderr); - } - fputs(strerror(errno), stderr); - fputs("\r\n", stderr); -} diff --git a/libc/gfx/_gfx.h b/libc/gfx/_gfx.h new file mode 100644 index 0000000..cf23588 --- /dev/null +++ b/libc/gfx/_gfx.h @@ -0,0 +1,109 @@ +/* + * _gfx.h — внутренности графической подсистемы (НЕ публичный заголовок). + * + * Общее состояние лежит в data-модулях (_gfx_state.c, _gfx_w3_state.c, + * _gfx_acc256.c, _gfx_g16_state.c, _gfx_font_state.c), helpers — в + * отдельных internal-модулях; см. docs/libc-split-asm-cases.md. + */ +#ifndef _GFX_INTERNAL_H +#define _GFX_INTERNAL_H + +#include + +/* ---- Общее состояние (_gfx_state.c) ------------------------------ */ + +/* Кэш SELPAGE: видимая и рисуемая страницы (0/1). */ +extern uint8_t _gfx_visible_page; +extern uint8_t _gfx_draw_page; + +/* Байт страницы W3 (0x50..0x5F) — какой видеобанк мапится при записи. */ +extern uint8_t _gfx_bank; + +/* CPU-адрес колонки 0 текущей draw-страницы: 0xC000 (page 0) или + * 0xC140 (page 1); каждый примитив использует его вместо константы. */ +extern uint16_t _gfx_addr_base; + +/* ---- Низкоуровневый видеорежим (video/videomode_raw.c) ----------- */ + +uint8_t _videomode_raw_get(void); +int _videomode_raw_set(uint8_t mode); + +/* ---- W3-скобки (_gfx_w3_video_begin/end.c, _gfx_w3_state.c) ------ * + * begin: DI + сохранить текущий W3 и замапить _gfx_bank; + * end: восстановить W3 + EI. Не реентерабельно (сейв в статике). */ + +extern uint8_t _gfx_saved_w3; + +void _gfx_w3_video_begin(void); +void _gfx_w3_video_end(void); + +/* ---- Raw-примитивы 256 цветов (mode 0x81) ------------------------- * + * "Raw" = W3-naive: вызывающий обязан обернуть последовательность в + * одну пару _gfx_w3_video_begin/_gfx_w3_video_end. */ + +void _gfx_putpixel256_raw(int x, int y, uint8_t color); +void _gfx_hline256_raw (int x, int y, int len, uint8_t color); +void _gfx_vline256_raw (int x, int y, int len, uint8_t color); +void _gfx_clear256_raw (uint8_t color); + +/* Скретч акселератора (_gfx_acc256.c) — общий для hline/vline/clear; + * однопоточно, IRQ выключены между begin/end. */ +extern uint8_t _gfx_acc_color; +extern uint8_t _gfx_acc_y; +extern uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */ +extern uint16_t _gfx_acc_addr; + +/* Горизонтальный Fill-burst: _gfx_acc_len байт с _gfx_acc_addr на + * строке _gfx_acc_y (W3 замаплен, DI активен). */ +void _gfx_hfill256_chunk(void); + +/* ---- Raw-примитивы 16 цветов (mode 0x82) -------------------------- */ + +void _gfx_putpixel16_raw(int x, int y, uint8_t color); +void _gfx_hline16_raw (int x, int y, int len, uint8_t color); +void _gfx_vline16_raw (int x, int y, int len, uint8_t color); +void _gfx_clear16_raw (uint8_t color); + +/* Скретч 16-цветных примитивов (_gfx_g16_state.c). */ +extern uint8_t _gfx_g16_y; +extern uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) */ +extern uint8_t _gfx_g16_nibble; /* цвет в нужной половине байта */ +extern uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */ +extern uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */ +extern uint16_t _gfx_g16_addr; + +/* Горизонтальный Fill-burst байтами _gfx_g16_byte (_gfx_hfill16.c). */ +void _gfx_hfill16_chunk(void); + +/* RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y) (_gfx_rmw16.c). */ +void _gfx_rmw16_pixel(void); + +/* ---- Шрифт (_gfx_font_state.c, _gfx_font_ensure.c) ---------------- * + * Формат: 256 глифов × 8 рядов, INTERLEAVED — offset = row*256 + char; + * биты MSB-first. См. memory/sprinter_font_format. */ + +#define _GFX_FONT_BYTES 2048 + +extern uint8_t _gfx_font_buf[_GFX_FONT_BYTES]; +extern const uint8_t *_gfx_font_ptr; +extern uint8_t _gfx_font_loaded; + +/* Ленивая инициализация: грузит системный шрифт при первом использовании. */ +void _gfx_font_ensure(void); + +/* ---- Текст 16 цветов: общая машинерия ----------------------------- * + * Данные в _gfx_t16_state.c, рендер ряда в _gfx_render_row16.c, + * pair-таблица в _gfx_t16_pair_build.c. */ + +extern uint8_t _gfx_t16_y; +extern uint8_t _gfx_t16_row; /* битовый паттерн ряда глифа */ +extern uint16_t _gfx_t16_addr; /* VRAM-адрес ряда */ +extern uint8_t _gfx_t16_pair[4]; /* LEFT*2+RIGHT → (LEFT<<4)|RIGHT */ + +/* Отрисовать один 8-пиксельный ряд по pair-таблице (4 записи байт). */ +void _gfx_render_row16(void); + +/* Заполнить pair-таблицу для пары цветов fg/bg. */ +void _gfx_t16_pair_build(uint8_t fg, uint8_t bg); + +#endif diff --git a/libc/gfx/_gfx_acc256.c b/libc/gfx/_gfx_acc256.c new file mode 100644 index 0000000..5c1ad77 --- /dev/null +++ b/libc/gfx/_gfx_acc256.c @@ -0,0 +1,12 @@ +/* + * _gfx_acc256 — скретч акселератора для 256-цветных raw-примитивов + * (модуль только с данными). Общий для hline/vline/clear; однопоточно — + * GFX работает с выключенными прерываниями между W3-begin/end. + */ + +#include "_gfx.h" + +uint8_t _gfx_acc_color; +uint8_t _gfx_acc_y; +uint8_t _gfx_acc_len; /* 0 = 256 — конвенция акселератора */ +uint16_t _gfx_acc_addr; diff --git a/libc/gfx/_gfx_clear16_raw.c b/libc/gfx/_gfx_clear16_raw.c new file mode 100644 index 0000000..94ddf97 --- /dev/null +++ b/libc/gfx/_gfx_clear16_raw.c @@ -0,0 +1,20 @@ +/* + * _gfx_clear16_raw — залить всё поле 640×256 цветом color, mode 0x82. + * Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта). W3-naive. + */ + +#include "_gfx.h" + +void _gfx_clear16_raw(uint8_t color) +{ + _gfx_g16_byte = (uint8_t)((color & 0x0F) | ((color & 0x0F) << 4)); + for (int y = 0; y < GFX_HEIGHT_16; y++) { + _gfx_g16_y = (uint8_t)y; + _gfx_g16_addr = _gfx_addr_base; + _gfx_g16_len = 0; + _gfx_hfill16_chunk(); + _gfx_g16_addr = (uint16_t)(_gfx_addr_base + 0x100); + _gfx_g16_len = 64; + _gfx_hfill16_chunk(); + } +} diff --git a/libc/gfx/_gfx_clear256_raw.c b/libc/gfx/_gfx_clear256_raw.c new file mode 100644 index 0000000..f15b442 --- /dev/null +++ b/libc/gfx/_gfx_clear256_raw.c @@ -0,0 +1,21 @@ +/* + * _gfx_clear256_raw — залить всё поле 320×256 цветом color, mode 0x81. + * Построчный hfill: 256 строк × 2 burst'а (256 + 64 байта) = 512. + * W3-naive: вызывающий обрамляет begin/end. + */ + +#include "_gfx.h" + +void _gfx_clear256_raw(uint8_t color) +{ + _gfx_acc_color = color; + for (int y = 0; y < GFX_HEIGHT; y++) { + _gfx_acc_y = (uint8_t)y; + _gfx_acc_addr = _gfx_addr_base; /* burst 256 байт */ + _gfx_acc_len = 0; + _gfx_hfill256_chunk(); + _gfx_acc_addr = (uint16_t)(_gfx_addr_base + 256); /* burst 64 байта */ + _gfx_acc_len = 64; + _gfx_hfill256_chunk(); + } +} diff --git a/libc/gfx/_gfx_font_ensure.c b/libc/gfx/_gfx_font_ensure.c new file mode 100644 index 0000000..26863e8 --- /dev/null +++ b/libc/gfx/_gfx_font_ensure.c @@ -0,0 +1,12 @@ +/* + * _gfx_font_ensure — ленивая инициализация шрифта: текстовые рендеры + * зовут её при каждом входе, системный шрифт грузится при первом + * использовании (чисто графическая программа BIOS-вызов не платит). + */ + +#include "_gfx.h" + +void _gfx_font_ensure(void) +{ + if (!_gfx_font_loaded) gfx_load_default_font(); +} diff --git a/libc/gfx/_gfx_font_state.c b/libc/gfx/_gfx_font_state.c new file mode 100644 index 0000000..4225c0a --- /dev/null +++ b/libc/gfx/_gfx_font_state.c @@ -0,0 +1,16 @@ +/* + * _gfx_font_state — буфер шрифта, указатель и флаг загрузки (данные). + * + * Формат (ZX-Spectrum-совместимый): 256 глифов × 8 рядов × 1 байт = 2 КБ, + * INTERLEAVED — offset = row*256 + char (ряд 0 всех глифов занимает + * 0x000..0x0FF и т.д.), биты MSB-first (бит 7 = левый пиксель). + * + * _gfx_font_ptr всегда указывает на валидные данные после + * _gfx_font_ensure(); программы могут подменить шрифт gfx_set_font(). + */ + +#include "_gfx.h" + +uint8_t _gfx_font_buf[_GFX_FONT_BYTES]; +const uint8_t *_gfx_font_ptr = _gfx_font_buf; +uint8_t _gfx_font_loaded; diff --git a/libc/gfx/_gfx_g16_state.c b/libc/gfx/_gfx_g16_state.c new file mode 100644 index 0000000..008191b --- /dev/null +++ b/libc/gfx/_gfx_g16_state.c @@ -0,0 +1,14 @@ +/* + * _gfx_g16_state — скретч 16-цветных raw-примитивов (только данные). + * Общий для putpixel/hline/vline/clear; однопоточно, IRQ выключены + * между W3-begin/end. + */ + +#include "_gfx.h" + +uint8_t _gfx_g16_y; +uint8_t _gfx_g16_byte; /* nibble | (nibble<<4) — цвет в обе половины */ +uint8_t _gfx_g16_nibble; /* цвет в нужной половине (low или high) */ +uint8_t _gfx_g16_mask; /* маска, сохраняющая ДРУГУЮ половину */ +uint8_t _gfx_g16_len; /* размер accel-блока (0 = 256) */ +uint16_t _gfx_g16_addr; diff --git a/libc/gfx/_gfx_hfill16.c b/libc/gfx/_gfx_hfill16.c new file mode 100644 index 0000000..dfa3162 --- /dev/null +++ b/libc/gfx/_gfx_hfill16.c @@ -0,0 +1,38 @@ +/* + * _gfx_hfill16_chunk — горизонтальный Fill-burst акселератора для + * mode 0x82: _gfx_g16_len байт значения _gfx_g16_byte с _gfx_g16_addr + * на строке _gfx_g16_y. Один burst красит до 256 байт = 512 пикселей. + * Pre: W3 замаплен, DI активен. + * + * SMC только у байта длины. Цвет предзагружается в C и уходит через + * `ld a, c` (0x79) — вставить ещё один `ld a,#n` между LD C,C и + * стреляющим LD (HL),A нельзя: FSM акселератора перечитает immediate + * как новый размер блока. + */ + +#include "_gfx.h" + +void _gfx_hfill16_chunk(void) __naked +{ + __asm + ld a, (__gfx_g16_len) + ld (_hfill16_len_imm), a + + ld a, (__gfx_g16_byte) + ld c, a + + ld a, (__gfx_g16_y) + out (#0x89), a + + ld hl, (__gfx_g16_addr) + + ld d, d ; 0x52 — режим размера блока + ld a, #0 ; 0x3E nn — длина (патчится) + _hfill16_len_imm = . - 1 + ld c, c ; 0x49 — горизонтальный Fill + ld a, c ; 0x79 — A = байт цвета + ld (hl), a ; выстрел + ld b, b ; 0x40 — выключить + ret + __endasm; +} diff --git a/libc/gfx/_gfx_hfill256.c b/libc/gfx/_gfx_hfill256.c new file mode 100644 index 0000000..09a0a29 --- /dev/null +++ b/libc/gfx/_gfx_hfill256.c @@ -0,0 +1,45 @@ +/* + * _gfx_hfill256_chunk — горизонтальный Fill-burst акселератора: + * _gfx_acc_len байт цвета _gfx_acc_color с адреса _gfx_acc_addr на + * строке _gfx_acc_y. Pre: W3 замаплен на видеобанк, DI активен. + * + * Опкоды акселератора (docs/converted/accel_r.txt): + * LD D,D (0x52) режим «задать размер блока»; СЛЕДУЮЩИЙ байт — длина, + * ОБЯЗАН быть immediate-операндом LD A,n (0x3E) + * LD C,C (0x49) горизонтальный Fill (LD (HL),A заливает n байт) + * LD B,B (0x40) выключить акселератор + * + * Байт длины патчится SMC — HOME после загрузки DSS это RAM, патчить + * собственный .EXE безопасно. Вставлять другой `ld a,#n` между LD C,C + * и стреляющим LD (HL),A нельзя — FSM акселератора перечитает immediate + * как новый размер блока. + */ + +#include "_gfx.h" + +void _gfx_hfill256_chunk(void) __naked +{ + __asm + ;; Пропатчить immediate LD A,#n байтом длины. + ld a, (__gfx_acc_len) + ld (_hfill256_len_imm), a + + ld a, (__gfx_acc_y) + out (#0x89), a ; Port_Y = y + + ;; Цвет в C и адрес в HL готовим ДО взведения акселератора. + ld a, (__gfx_acc_color) + ld c, a + ld hl, (__gfx_acc_addr) + + ;; --- ПОСЛЕДОВАТЕЛЬНОСТЬ АКСЕЛЕРАТОРА --- + ld d, d ; 0x52 — режим размера блока + ld a, #0 ; 0x3E nn — размер (nn патчится выше) + _hfill256_len_imm = . - 1 + ld c, c ; 0x49 — горизонтальный Fill + ld a, c ; 0x79 — A = цвет (НЕ ещё один ld a,#n) + ld (hl), a ; выстрел; заливает _gfx_acc_len байт + ld b, b ; 0x40 — выключить + ret + __endasm; +} diff --git a/libc/gfx/_gfx_hline16_raw.c b/libc/gfx/_gfx_hline16_raw.c new file mode 100644 index 0000000..83b95dd --- /dev/null +++ b/libc/gfx/_gfx_hline16_raw.c @@ -0,0 +1,50 @@ +/* + * _gfx_hline16_raw — горизонтальная линия len пикселей от (x,y), + * mode 0x82: невыровненные крайние ниблы через RMW, середина — + * Fill-burst'ами акселератора по целым байтам. W3-naive. + */ + +#include "_gfx.h" + +void _gfx_hline16_raw(int x, int y, int len, uint8_t color) +{ + if ((unsigned)y >= GFX_HEIGHT_16) return; + if (x < 0) { len += x; x = 0; } + if (x >= GFX_WIDTH_16) return; + if (x + len > GFX_WIDTH_16) len = GFX_WIDTH_16 - x; + if (len <= 0) return; + + _gfx_g16_y = (uint8_t)y; + uint8_t cnib = color & 0x0F; + _gfx_g16_byte = (uint8_t)(cnib | (cnib << 4)); + + /* Ведущий невыровненный пиксель: x нечётный — ПРАВАЯ половина + * крайнего левого байта. */ + if (x & 1) { + _gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); + _gfx_g16_nibble = cnib; + _gfx_g16_mask = 0xF0; + _gfx_rmw16_pixel(); + x++; + len--; + if (len <= 0) return; + } + + /* x чётный; len/2 целых байтов через accel hfill. */ + int full = len >> 1; + _gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); + while (full > 0) { + int chunk = full > 256 ? 256 : full; + _gfx_g16_len = (chunk == 256) ? 0 : (uint8_t)chunk; + _gfx_hfill16_chunk(); + full -= chunk; + _gfx_g16_addr += chunk; + } + + /* Хвостовой пиксель нечётной длины: ЛЕВАЯ половина след. байта. */ + if (len & 1) { + _gfx_g16_nibble = (uint8_t)(cnib << 4); + _gfx_g16_mask = 0x0F; + _gfx_rmw16_pixel(); + } +} diff --git a/libc/gfx/_gfx_hline256_raw.c b/libc/gfx/_gfx_hline256_raw.c new file mode 100644 index 0000000..87e852f --- /dev/null +++ b/libc/gfx/_gfx_hline256_raw.c @@ -0,0 +1,28 @@ +/* + * _gfx_hline256_raw — горизонтальная линия len пикселей от (x,y), + * mode 0x81, через Fill-burst'ы акселератора (до 256 байт за выстрел). + * W3-naive: вызывающий обрамляет begin/end. + */ + +#include "_gfx.h" + +void _gfx_hline256_raw(int x, int y, int len, uint8_t color) +{ + if ((unsigned)y >= GFX_HEIGHT) return; + if (x < 0) { len += x; x = 0; } + if (x >= GFX_WIDTH) return; + if (x + len > GFX_WIDTH) len = GFX_WIDTH - x; + if (len <= 0) return; + + _gfx_acc_color = color; + _gfx_acc_y = (uint8_t)y; + _gfx_acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x); + + while (len > 0) { + int chunk = len > 256 ? 256 : len; + _gfx_acc_len = (chunk == 256) ? 0 : (uint8_t)chunk; + _gfx_hfill256_chunk(); + len -= chunk; + _gfx_acc_addr += chunk; + } +} diff --git a/libc/gfx/_gfx_putpixel16_raw.c b/libc/gfx/_gfx_putpixel16_raw.c new file mode 100644 index 0000000..46a84b4 --- /dev/null +++ b/libc/gfx/_gfx_putpixel16_raw.c @@ -0,0 +1,22 @@ +/* + * _gfx_putpixel16_raw — пиксель (x,y) цветом 0..15, mode 0x82. + * Байт по адресу _gfx_addr_base + x/2 держит два пикселя: high nibble = + * ЛЕВЫЙ (чётный x), low = ПРАВЫЙ (нечётный). W3-naive. + */ + +#include "_gfx.h" + +void _gfx_putpixel16_raw(int x, int y, uint8_t color) +{ + if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return; + _gfx_g16_y = (uint8_t)y; + _gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); + if (x & 1) { + _gfx_g16_nibble = (uint8_t)(color & 0x0F); + _gfx_g16_mask = 0xF0; + } else { + _gfx_g16_nibble = (uint8_t)((color & 0x0F) << 4); + _gfx_g16_mask = 0x0F; + } + _gfx_rmw16_pixel(); +} diff --git a/libc/gfx/_gfx_putpixel256_raw.c b/libc/gfx/_gfx_putpixel256_raw.c new file mode 100644 index 0000000..6c60d61 --- /dev/null +++ b/libc/gfx/_gfx_putpixel256_raw.c @@ -0,0 +1,30 @@ +/* + * _gfx_putpixel256_raw — пиксель (x,y) цветом color, mode 0x81. + * + * "Raw" = W3-naive: вызывающий уже сделал _gfx_w3_video_begin (W3 на + * видеобанке, DI). Адресация: CPU _gfx_addr_base + x, Port_Y (0x89) = y. + * + * Скретч отдельный от _gfx_acc_* — чтобы putpixel внутри цикла + * Брезенхэма не топтал внешнее состояние акселератора. + */ + +#include "_gfx.h" + +static uint8_t _gfx_pp_y; +static uint16_t _gfx_pp_addr; +static uint8_t _gfx_pp_color; + +void _gfx_putpixel256_raw(int x, int y, uint8_t color) +{ + if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return; + _gfx_pp_y = (uint8_t)y; + _gfx_pp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x); + _gfx_pp_color = color; + __asm + ld a, (__gfx_pp_y) + out (#0x89), a + ld hl, (__gfx_pp_addr) + ld a, (__gfx_pp_color) + ld (hl), a + __endasm; +} diff --git a/libc/gfx/_gfx_render_row16.c b/libc/gfx/_gfx_render_row16.c new file mode 100644 index 0000000..257a1cd --- /dev/null +++ b/libc/gfx/_gfx_render_row16.c @@ -0,0 +1,53 @@ +/* + * _gfx_render_row16 — один 8-пиксельный ряд глифа в mode 0x82 + * (640×256×16): 4 выходных байта, каждый пакует два соседних пикселя + * как (LEFT<<4) | RIGHT через прекомпилированную _gfx_t16_pair — + * 2-битный паттерн источника (LEFT*2 + RIGHT) сразу даёт выходной + * байт, без mask/OR на пиксель. Сам делает DI / W3 save+restore — + * вызывать можно из любого контекста без скобок. + */ + +#include "_gfx.h" + +void _gfx_render_row16(void) __naked +{ + __asm + di + in a, (#0xE2) + push af + ld a, #0x50 + out (#0xE2), a + ld a, (__gfx_t16_y) + out (#0x89), a + + ld hl, (__gfx_t16_addr) + ld a, (__gfx_t16_row) + ld c, a ; C = сдвигаемые биты источника + ld b, #4 ; 4 выходных байта + rr16_loop: + ;; Два верхних бита C в индекс A = LEFT*2 + RIGHT. + sla c ; CY = бит LEFT; C <<= 1 + ld a, #0 + adc a, a ; A = LEFT + sla c ; CY = бит RIGHT; C <<= 1 + adc a, a ; A = LEFT*2 + RIGHT (0..3) + + ;; A = _gfx_t16_pair[A]; HL сохраняем через стек. + push hl + ld e, a + ld d, #0 + ld hl, #__gfx_t16_pair + add hl, de + ld a, (hl) + pop hl + + ld (hl), a + inc hl + djnz rr16_loop + + pop af + out (#0xE2), a + ei + ret + __endasm; +} diff --git a/libc/gfx/_gfx_rmw16.c b/libc/gfx/_gfx_rmw16.c new file mode 100644 index 0000000..f3a179c --- /dev/null +++ b/libc/gfx/_gfx_rmw16.c @@ -0,0 +1,28 @@ +/* + * _gfx_rmw16_pixel — RMW одного нибла по (_gfx_g16_addr, _gfx_g16_y): + * прочитать байт, стереть целевой нибл маской _gfx_g16_mask, вписать + * _gfx_g16_nibble. ~10 инструкций на пиксель — медленно, но байт + * покрывает два горизонтальных пикселя, иначе никак. + * Pre: W3 замаплен, DI активен. + */ + +#include "_gfx.h" + +void _gfx_rmw16_pixel(void) __naked +{ + __asm + ld a, (__gfx_g16_y) + out (#0x89), a + + ld hl, (__gfx_g16_addr) + ld a, (__gfx_g16_mask) + ld b, a ; B = маска сохранения + ld a, (__gfx_g16_nibble) + ld c, a ; C = новый нибл (в нужной половине) + ld a, (hl) + and a, b ; стереть целевой нибл + or a, c ; вписать новый + ld (hl), a + ret + __endasm; +} diff --git a/libc/gfx/_gfx_state.c b/libc/gfx/_gfx_state.c new file mode 100644 index 0000000..b98d68f --- /dev/null +++ b/libc/gfx/_gfx_state.c @@ -0,0 +1,20 @@ +/* + * _gfx_state — общее состояние графики (модуль только с данными). + * + * Пишут gfx_init / gfx_set_visible_page / gfx_set_draw_page / + * gfx_set_bank; читают все mode-специфичные примитивы. + */ + +#include "_gfx.h" + +uint8_t _gfx_visible_page; +uint8_t _gfx_draw_page; + +/* См. memory/sprinter_vram_transparency.md про биты 0x50..0x5F + * (0x50 normal, 0x54 temp, 0x58 transparent, 0x5C оба). */ +uint8_t _gfx_bank = 0x50; + +/* Каждая VRAM-строка — 1024 байта: page 0 занимает байты 0..319 + * (CPU 0xC000+), page 1 — 320..639 (CPU 0xC140+), остаток — дескрипторы + * режима/палитра, их не трогаем. */ +uint16_t _gfx_addr_base = 0xC000; diff --git a/libc/gfx/_gfx_t16_pair_build.c b/libc/gfx/_gfx_t16_pair_build.c new file mode 100644 index 0000000..31726a2 --- /dev/null +++ b/libc/gfx/_gfx_t16_pair_build.c @@ -0,0 +1,17 @@ +/* + * _gfx_t16_pair_build — заполнить pair-таблицу текстового рендера + * mode 0x82 для пары цветов fg/bg. + */ + +#include "_gfx.h" + +void _gfx_t16_pair_build(uint8_t fg, uint8_t bg) +{ + uint8_t f = fg & 0x0F; + uint8_t b = bg & 0x0F; + /* HIGH nibble = LEFT, LOW = RIGHT (конвенция sprinter_graphics). */ + _gfx_t16_pair[0] = (uint8_t)((b << 4) | b); /* 00 BG BG */ + _gfx_t16_pair[1] = (uint8_t)((b << 4) | f); /* 01 BG FG */ + _gfx_t16_pair[2] = (uint8_t)((f << 4) | b); /* 10 FG BG */ + _gfx_t16_pair[3] = (uint8_t)((f << 4) | f); /* 11 FG FG */ +} diff --git a/libc/gfx/_gfx_t16_state.c b/libc/gfx/_gfx_t16_state.c new file mode 100644 index 0000000..3c9d80d --- /dev/null +++ b/libc/gfx/_gfx_t16_state.c @@ -0,0 +1,14 @@ +/* + * _gfx_t16_state — скретч текстового рендера mode 0x82 (только данные). + * Пишут gfx_putchar16/gfx_text16 и _gfx_t16_pair_build, читает + * _gfx_render_row16. + */ + +#include "_gfx.h" + +uint8_t _gfx_t16_y; +uint8_t _gfx_t16_row; +uint16_t _gfx_t16_addr; + +/* index = LEFT*2 + RIGHT, value = (LEFT<<4)|RIGHT. */ +uint8_t _gfx_t16_pair[4]; diff --git a/libc/gfx/_gfx_vline16_raw.c b/libc/gfx/_gfx_vline16_raw.c new file mode 100644 index 0000000..ea61ba6 --- /dev/null +++ b/libc/gfx/_gfx_vline16_raw.c @@ -0,0 +1,30 @@ +/* + * _gfx_vline16_raw — вертикальная линия len пикселей от (x,y), + * mode 0x82. Вертикального accel-пути нет: байт покрывает два + * горизонтальных пикселя, Fill затёр бы соседний нибл — поэтому + * RMW на каждую строку. W3-naive. + */ + +#include "_gfx.h" + +void _gfx_vline16_raw(int x, int y, int len, uint8_t color) +{ + if ((unsigned)x >= GFX_WIDTH_16) return; + if (y < 0) { len += y; y = 0; } + if (y >= GFX_HEIGHT_16) return; + if (y + len > GFX_HEIGHT_16) len = GFX_HEIGHT_16 - y; + if (len <= 0) return; + + _gfx_g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); + if (x & 1) { + _gfx_g16_nibble = (uint8_t)(color & 0x0F); + _gfx_g16_mask = 0xF0; + } else { + _gfx_g16_nibble = (uint8_t)((color & 0x0F) << 4); + _gfx_g16_mask = 0x0F; + } + for (int i = 0; i < len; i++) { + _gfx_g16_y = (uint8_t)(y + i); + _gfx_rmw16_pixel(); + } +} diff --git a/libc/gfx/_gfx_vline256_raw.c b/libc/gfx/_gfx_vline256_raw.c new file mode 100644 index 0000000..9c71cb0 --- /dev/null +++ b/libc/gfx/_gfx_vline256_raw.c @@ -0,0 +1,50 @@ +/* + * _gfx_vline256_raw — вертикальная линия len пикселей от (x,y), + * mode 0x81. Вертикальный Fill акселератора сам инкрементирует Port_Y, + * так что вся колонка (высота 256) — один burst. W3-naive. + */ + +#include "_gfx.h" + +/* Вертикальный Fill: _gfx_acc_len пикселей в колонке _gfx_acc_addr, + * верхняя строка _gfx_acc_y. Про SMC и правила FSM акселератора — + * см. _gfx_hfill256.c. */ +static void vfill_chunk(void) __naked +{ + __asm + ld a, (__gfx_acc_len) + ld (_vfill256_len_imm), a + + ld a, (__gfx_acc_y) + out (#0x89), a ; стартовый Y + + ld a, (__gfx_acc_color) + ld c, a + ld hl, (__gfx_acc_addr) + + ld d, d ; 0x52 — режим размера блока + ld a, #0 ; immediate-длина (патчится) + _vfill256_len_imm = . - 1 + ld e, e ; 0x5B — вертикальный Fill + ld a, c ; A = цвет + ld (hl), a ; выстрел + ld b, b ; 0x40 — выключить + ret + __endasm; +} + +void _gfx_vline256_raw(int x, int y, int len, uint8_t color) +{ + if ((unsigned)x >= GFX_WIDTH) return; + if (y < 0) { len += y; y = 0; } + if (y >= GFX_HEIGHT) return; + if (y + len > GFX_HEIGHT) len = GFX_HEIGHT - y; + if (len <= 0) return; + + /* GFX_HEIGHT = 256, полная колонка — один accel-burst. */ + _gfx_acc_color = color; + _gfx_acc_y = (uint8_t)y; + _gfx_acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x); + _gfx_acc_len = (len == 256) ? 0 : (uint8_t)len; + vfill_chunk(); +} diff --git a/libc/gfx/_gfx_w3_state.c b/libc/gfx/_gfx_w3_state.c new file mode 100644 index 0000000..7ff33a6 --- /dev/null +++ b/libc/gfx/_gfx_w3_state.c @@ -0,0 +1,10 @@ +/* + * _gfx_w3_state — сохранённый байт W3 между begin и end (данные). + * + * НЕ реентерабельно — безопасно только потому, что GFX держит + * прерывания выключенными между _gfx_w3_video_begin и _end. + */ + +#include "_gfx.h" + +uint8_t _gfx_saved_w3; diff --git a/libc/gfx/_gfx_w3_video_begin.c b/libc/gfx/_gfx_w3_video_begin.c new file mode 100644 index 0000000..4a6e271 --- /dev/null +++ b/libc/gfx/_gfx_w3_video_begin.c @@ -0,0 +1,20 @@ +/* + * _gfx_w3_video_begin — DI, сохранить текущую страницу W3 и замапить + * _gfx_bank (видеобанк 0x50..0x5F). Композитные примитивы обрамляют + * одной парой begin/end весь внутренний цикл — W3-танец оплачивается + * один раз на операцию, а не на пиксель. + */ + +#include "_gfx.h" + +void _gfx_w3_video_begin(void) __naked +{ + __asm + di + in a, (#0xE2) + ld (__gfx_saved_w3), a + ld a, (__gfx_bank) + out (#0xE2), a + ret + __endasm; +} diff --git a/libc/gfx/_gfx_w3_video_end.c b/libc/gfx/_gfx_w3_video_end.c new file mode 100644 index 0000000..2cf90ac --- /dev/null +++ b/libc/gfx/_gfx_w3_video_end.c @@ -0,0 +1,15 @@ +/* + * _gfx_w3_video_end — восстановить сохранённую страницу W3 и EI. + */ + +#include "_gfx.h" + +void _gfx_w3_video_end(void) __naked +{ + __asm + ld a, (__gfx_saved_w3) + out (#0xE2), a + ei + ret + __endasm; +} diff --git a/libc/gfx/gfx_16.c b/libc/gfx/gfx_16.c deleted file mode 100644 index da36869..0000000 --- a/libc/gfx/gfx_16.c +++ /dev/null @@ -1,106 +0,0 @@ -/* - * gfx_16.c — 16-colour (mode 0x82) public drawing API. - * - * Raw primitives live in gfx_raw_16.c. Single-shot wrappers do one - * W3-begin / one W3-end around their raw call; composites wrap many - * raw calls in a single begin/end pair so the W3 dance is amortised. - * - * No vertical-accelerator path: in 16-colour mode each byte spans two - * horizontal pixels, so a vertical Fill burst would also affect the - * other column's nibble — vline16 falls back to per-row RMW. - */ - -#include -#include - -extern void _gfx_w3_video_begin(void); -extern void _gfx_w3_video_end(void); - -extern void _gfx_putpixel16_raw(int x, int y, uint8_t color); -extern void _gfx_hline16_raw (int x, int y, int len, uint8_t color); -extern void _gfx_vline16_raw (int x, int y, int len, uint8_t color); -extern void _gfx_clear16_raw (uint8_t color); - -void gfx_clear16(uint8_t color) -{ - _gfx_w3_video_begin(); - _gfx_clear16_raw(color); - _gfx_w3_video_end(); -} - -void gfx_putpixel16(int x, int y, uint8_t color) -{ - if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return; - _gfx_w3_video_begin(); - _gfx_putpixel16_raw(x, y, color); - _gfx_w3_video_end(); -} - -void gfx_hline16(int x, int y, int len, uint8_t color) -{ - _gfx_w3_video_begin(); - _gfx_hline16_raw(x, y, len, color); - _gfx_w3_video_end(); -} - -void gfx_vline16(int x, int y, int len, uint8_t color) -{ - _gfx_w3_video_begin(); - _gfx_vline16_raw(x, y, len, color); - _gfx_w3_video_end(); -} - -void gfx_rect16(int x, int y, int w, int h, uint8_t color) -{ - if (w <= 0 || h <= 0) return; - _gfx_w3_video_begin(); - _gfx_hline16_raw(x, y, w, color); - _gfx_hline16_raw(x, y + h - 1, w, color); - if (h > 2) { - _gfx_vline16_raw(x, y + 1, h - 2, color); - _gfx_vline16_raw(x + w - 1, y + 1, h - 2, color); - } - _gfx_w3_video_end(); -} - -void gfx_fill_rect16(int x, int y, int w, int h, uint8_t color) -{ - if (w <= 0 || h <= 0) return; - /* Column-major vlines — user's earlier request: 16-color rect_fill - * via vertical lines. Each column does its own per-row RMW; the - * outer begin/end wraps the whole rect so W3 is mapped once. */ - _gfx_w3_video_begin(); - for (int xx = 0; xx < w; xx++) - _gfx_vline16_raw(x + xx, y, h, color); - _gfx_w3_video_end(); -} - -void gfx_line16(int x0, int y0, int x1, int y1, uint8_t color) -{ - if (y0 == y1) { - int x = x0 <= x1 ? x0 : x1; - int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1; - gfx_hline16(x, y0, w, color); - return; - } - if (x0 == x1) { - int y = y0 <= y1 ? y0 : y1; - int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1; - gfx_vline16(x0, y, h, color); - return; - } - /* Bresenham — single W3 setup around the whole loop. */ - int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx; - int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy; - int err = (dx > dy ? dx : -dy) / 2; - int x = x0, y = y0; - _gfx_w3_video_begin(); - for (;;) { - _gfx_putpixel16_raw(x, y, color); - if (x == x1 && y == y1) break; - int e2 = err; - if (e2 > -dx) { err -= dy; x += sx; } - if (e2 < dy) { err += dx; y += sy; } - } - _gfx_w3_video_end(); -} diff --git a/libc/gfx/gfx_256.c b/libc/gfx/gfx_256.c deleted file mode 100644 index f20dd25..0000000 --- a/libc/gfx/gfx_256.c +++ /dev/null @@ -1,118 +0,0 @@ -/* - * gfx_256.c — 256-colour (mode 0x81) public drawing API. - * - * Single-shot wrappers do one W3-begin / one W3-end around their raw - * call. Composites (rect, fill_rect, line) wrap a single begin/end - * around many raw calls so the W3 dance is paid once per operation, - * not once per pixel/byte. - */ - -#include -#include - -/* From gfx_raw_common.c — DI + map _gfx_bank into W3, save previous. */ -extern void _gfx_w3_video_begin(void); -extern void _gfx_w3_video_end(void); - -/* From gfx_raw_256.c — W3-naive primitives. */ -extern void _gfx_putpixel256_raw(int x, int y, uint8_t color); -extern void _gfx_hline256_raw (int x, int y, int len, uint8_t color); -extern void _gfx_vline256_raw (int x, int y, int len, uint8_t color); -extern void _gfx_clear256_raw (uint8_t color); - -void gfx_clear256(uint8_t color) -{ - _gfx_w3_video_begin(); - _gfx_clear256_raw(color); - _gfx_w3_video_end(); -} - -void gfx_putpixel256(int x, int y, uint8_t color) -{ - if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return; - _gfx_w3_video_begin(); - _gfx_putpixel256_raw(x, y, color); - _gfx_w3_video_end(); -} - -void gfx_hline256(int x, int y, int len, uint8_t color) -{ - _gfx_w3_video_begin(); - _gfx_hline256_raw(x, y, len, color); - _gfx_w3_video_end(); -} - -void gfx_vline256(int x, int y, int len, uint8_t color) -{ - _gfx_w3_video_begin(); - _gfx_vline256_raw(x, y, len, color); - _gfx_w3_video_end(); -} - -void gfx_rect256(int x, int y, int w, int h, uint8_t color) -{ - if (w <= 0 || h <= 0) return; - _gfx_w3_video_begin(); - _gfx_hline256_raw(x, y, w, color); - _gfx_hline256_raw(x, y + h - 1, w, color); - if (h > 2) { - _gfx_vline256_raw(x, y + 1, h - 2, color); - _gfx_vline256_raw(x + w - 1, y + 1, h - 2, color); - } - _gfx_w3_video_end(); -} - -void gfx_fill_rect256(int x, int y, int w, int h, uint8_t color) -{ - if (w <= 0 || h <= 0) return; - - /* Pick the orientation with fewer accelerator bursts. Each burst - * paints up to 256 contiguous bytes (horizontal) or up to 256 - * vertical pixels in one column. - * row-major (hlines): h × ceil(w/256) bursts - * col-major (vlines): w × ceil(h/256) bursts — for h ≤ 256 = w - * Vertical wins for tall-narrow rects; horizontal for short-wide. */ - int h_bursts = h * ((w + 255) >> 8); - int v_bursts = w * ((h + 255) >> 8); - _gfx_w3_video_begin(); - if (h_bursts <= v_bursts) { - for (int yy = 0; yy < h; yy++) - _gfx_hline256_raw(x, y + yy, w, color); - } else { - for (int xx = 0; xx < w; xx++) - _gfx_vline256_raw(x + xx, y, h, color); - } - _gfx_w3_video_end(); -} - -void gfx_line256(int x0, int y0, int x1, int y1, uint8_t color) -{ - /* Orthogonal lines route to the accelerator. */ - if (y0 == y1) { - int x = x0 <= x1 ? x0 : x1; - int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1; - gfx_hline256(x, y0, w, color); - return; - } - if (x0 == x1) { - int y = y0 <= y1 ? y0 : y1; - int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1; - gfx_vline256(x0, y, h, color); - return; - } - - /* Bresenham — single W3-setup around the whole loop. */ - int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx; - int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy; - int err = (dx > dy ? dx : -dy) / 2; - int x = x0, y = y0; - _gfx_w3_video_begin(); - for (;;) { - _gfx_putpixel256_raw(x, y, color); - if (x == x1 && y == y1) break; - int e2 = err; - if (e2 > -dx) { err -= dy; x += sx; } - if (e2 < dy) { err += dx; y += sy; } - } - _gfx_w3_video_end(); -} diff --git a/libc/gfx/gfx_clear16.c b/libc/gfx/gfx_clear16.c new file mode 100644 index 0000000..62925b5 --- /dev/null +++ b/libc/gfx/gfx_clear16.c @@ -0,0 +1,12 @@ +/* + * gfx_clear16 — залить экран цветом (mode 0x82); одна W3-скобка. + */ + +#include "_gfx.h" + +void gfx_clear16(uint8_t color) +{ + _gfx_w3_video_begin(); + _gfx_clear16_raw(color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_clear256.c b/libc/gfx/gfx_clear256.c new file mode 100644 index 0000000..4c00ab8 --- /dev/null +++ b/libc/gfx/gfx_clear256.c @@ -0,0 +1,12 @@ +/* + * gfx_clear256 — залить экран цветом (mode 0x81); одна W3-скобка. + */ + +#include "_gfx.h" + +void gfx_clear256(uint8_t color) +{ + _gfx_w3_video_begin(); + _gfx_clear256_raw(color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_core.c b/libc/gfx/gfx_core.c deleted file mode 100644 index 9157fc8..0000000 --- a/libc/gfx/gfx_core.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * gfx_core.c — Sprinter graphics: common state + setup/teardown API. - * - * This module owns the variables that select which graphics page is - * visible vs drawn into, which W3 bank is mapped during gfx writes, - * and the row-base CPU address. All mode-specific primitives (256- - * color in gfx_raw_256.c / gfx_256.c, 16-color in gfx_raw_16.c / - * gfx_16.c, text in gfx_text_*.c) read this state via extern. - * - * Public API: - * gfx_init / gfx_done — switch video mode, restore previous - * gfx_set_visible_page / get — ESTEX $54 SELPAGE wrapper + cached value - * gfx_set_draw_page / get — updates _gfx_addr_base for the new page - * gfx_set_bank / get — sets the W3 page byte (0x50..0x5F) - * gfx_wait_vsync — EI; HALT until next frame interrupt - * (palette wrappers live in gfx_palette.c, backed by libc/video/palette.c) - * - * Shared state (extern from this file): - * _gfx_addr_base — 0xC000 for page 0, 0xC140 for page 1. Every - * mode-specific primitive uses this instead of a - * hard-coded 0xC000 so the same code targets the - * currently-selected draw page. - * _gfx_bank — read by _gfx_w3_video_begin in gfx_raw_common.c - * to map the right W3 page. - */ - -#include -#include - -/* From conio's videomode_raw.c — bypasses set_videotextmode's text-only - * validation so gfx_init can move INTO graphics modes. */ -extern uint8_t _videomode_raw_get(void); -extern int _videomode_raw_set(uint8_t mode); - -/* ---- Shared graphics state --------------------------------------- */ - -/* Cached values of the SELPAGE state. Page numbers are 0 or 1. */ -static uint8_t _gfx_visible_page = 0; -static uint8_t _gfx_draw_page = 0; - -/* The W3 page byte (0x50..0x5F) — see memory/sprinter_vram_transparency.md - * for the bit-meanings (0x50 normal, 0x54 temp, 0x58 transparent, 0x5C - * both). Read by _gfx_w3_video_begin in gfx_raw_common.c. */ -uint8_t _gfx_bank = 0x50; - -/* CPU address of column 0 in the current draw page. Each VRAM row is - * 1024 bytes wide — page 0 occupies bytes 0..319 (CPU 0xC000+), page 1 - * occupies 320..639 (CPU 0xC140+), and the remaining 384 bytes hold - * mode descriptors / palette data we don't touch. Updated by - * gfx_set_draw_page; read by every primitive's raw helper. */ -uint16_t _gfx_addr_base = 0xC000; - -/* ---- gfx_init / gfx_done ----------------------------------------- */ - -uint8_t gfx_init(uint8_t mode, uint8_t page) -{ - uint8_t prev = _videomode_raw_get(); - _videomode_raw_set(mode); - _gfx_bank = 0x50; - gfx_set_visible_page(page); - gfx_set_draw_page(page); - return prev; -} - -void gfx_done(uint8_t mode) -{ - _videomode_raw_set(mode); -} - -/* ---- Visible page (ESTEX $54 SELPAGE) ---------------------------- * - * - * Direct OUT to port 0xC9 bit 0 toggles the "screen mode page" register - * but doesn't notify DSS's display bookkeeping, leaving the screen in - * an inconsistent state (one of the swaps shows as black). Going - * through the syscall keeps DSS happy. - */ -void gfx_set_visible_page(uint8_t page) -{ - _gfx_visible_page = page & 1; - __asm - push ix - ld a, (__gfx_visible_page) - ld b, a ; B = page 0/1 - ld c, #0x54 ; ESTEX SELPAGE - rst #0x10 - pop ix - __endasm; -} - -uint8_t gfx_get_visible_page(void) -{ - return _gfx_visible_page; -} - -/* ---- Draw page --------------------------------------------------- */ - -void gfx_set_draw_page(uint8_t page) -{ - _gfx_draw_page = page & 1; - /* Direct constants beat (0xC000 + (cond ? 0x140 : 0)) by 3 Z80 - * instructions — SDCC doesn't fold the constant addition. */ - _gfx_addr_base = _gfx_draw_page ? 0xC140 : 0xC000; -} - -uint8_t gfx_get_draw_page(void) -{ - return _gfx_draw_page; -} - -/* ---- W3 bank (0x50..0x5F) ---------------------------------------- */ - -void gfx_set_bank(uint8_t bank) -{ - _gfx_bank = bank; -} - -uint8_t gfx_get_bank(void) -{ - return _gfx_bank; -} - -/* ---- Frame sync -------------------------------------------------- * - * - * Block until the next IM2 frame interrupt (50 Hz, programmed by DSS - * for keyboard / cursor maintenance). The Z80's HALT instruction - * sleeps the CPU until the next IRQ, which DSS handles and returns - * to the instruction after HALT — that's the start of the vertical - * retrace window, ideal for a tear-free page swap. - */ -void gfx_wait_vsync(void) __naked -{ - __asm - ei - halt - ret - __endasm; -} - -/* Palette wrappers (gfx_pal_load / gfx_pal_set / gfx_pal_get / - * gfx_pal_get_color / gfx_pal_reset) moved to gfx_palette.c — see also - * libc/video/palette.c for the shared low-level $A4 / $A6 implementation. */ diff --git a/libc/gfx/gfx_done.c b/libc/gfx/gfx_done.c new file mode 100644 index 0000000..953c453 --- /dev/null +++ b/libc/gfx/gfx_done.c @@ -0,0 +1,10 @@ +/* + * gfx_done — восстановить ранее сохранённый видеорежим (из gfx_init). + */ + +#include "_gfx.h" + +void gfx_done(uint8_t mode) +{ + _videomode_raw_set(mode); +} diff --git a/libc/gfx/gfx_fill_rect16.c b/libc/gfx/gfx_fill_rect16.c new file mode 100644 index 0000000..93a3ee2 --- /dev/null +++ b/libc/gfx/gfx_fill_rect16.c @@ -0,0 +1,16 @@ +/* + * gfx_fill_rect16 — залитый прямоугольник w×h от (x,y) (mode 0x82). + * Поколоночные vlines (per-row RMW в каждой колонке); внешняя + * begin/end-скобка мапит W3 один раз на весь прямоугольник. + */ + +#include "_gfx.h" + +void gfx_fill_rect16(int x, int y, int w, int h, uint8_t color) +{ + if (w <= 0 || h <= 0) return; + _gfx_w3_video_begin(); + for (int xx = 0; xx < w; xx++) + _gfx_vline16_raw(x + xx, y, h, color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_fill_rect256.c b/libc/gfx/gfx_fill_rect256.c new file mode 100644 index 0000000..fdbd849 --- /dev/null +++ b/libc/gfx/gfx_fill_rect256.c @@ -0,0 +1,28 @@ +/* + * gfx_fill_rect256 — залитый прямоугольник w×h от (x,y) (mode 0x81). + */ + +#include "_gfx.h" + +void gfx_fill_rect256(int x, int y, int w, int h, uint8_t color) +{ + if (w <= 0 || h <= 0) return; + + /* Ориентация с меньшим числом accel-burst'ов. Один burst красит + * до 256 смежных байт (горизонталь) или до 256 пикселей колонки. + * построчно (hlines): h × ceil(w/256) burst'ов + * поколоночно (vlines): w × ceil(h/256) — при h ≤ 256 это w + * Вертикаль выигрывает на высоких узких, горизонталь — на низких + * широких. */ + int h_bursts = h * ((w + 255) >> 8); + int v_bursts = w * ((h + 255) >> 8); + _gfx_w3_video_begin(); + if (h_bursts <= v_bursts) { + for (int yy = 0; yy < h; yy++) + _gfx_hline256_raw(x, y + yy, w, color); + } else { + for (int xx = 0; xx < w; xx++) + _gfx_vline256_raw(x + xx, y, h, color); + } + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_font.c b/libc/gfx/gfx_font.c deleted file mode 100644 index 88e28dc..0000000 --- a/libc/gfx/gfx_font.c +++ /dev/null @@ -1,63 +0,0 @@ -/* - * gfx_font.c — bitmap-font management shared by the 256- and 16-colour - * text renderers. - * - * Format (ZX-Spectrum-compatible): 256 glyphs × 8 rows × 1 byte = 2 KB, - * INTERLEAVED row-major — - * offset = row * 256 + char_code - * so row 0 of every glyph occupies bytes 0x000..0x0FF, row 1 occupies - * 0x100..0x1FF, etc. Each row byte is MSB-first (bit 7 = leftmost px). - * - * The default source is BIOS WIN_GET_ZG (fn 0xB8) — the active system - * character generator. Programs may override with gfx_set_font(). - * - * Lazy initialisation: gfx_text_256.c / gfx_text_16.c call _gfx_font_ensure - * on first use so a pure-graphics program doesn't pay the BIOS call. - * - * The font pointer and the buffer are exported to the two text renderers - * via _gfx_font_ptr (extern), which always points at valid data once - * _gfx_font_ensure has been called. - */ - -#include -#include - -#define FONT_BYTES 2048 - -static uint8_t _gfx_font_buf[FONT_BYTES]; -const uint8_t *_gfx_font_ptr = _gfx_font_buf; -static uint8_t _gfx_font_loaded = 0; - -/* BIOS WIN_GET_ZG (0xB8): DE = destination, returns 2 KB. */ -static void bios_get_zg(uint8_t *dest) __naked -{ - (void)dest; - __asm - push ix ; BIOS clobbers IX - ex de, hl ; DE = dest (HL was arg) - xor a ; font 0 (default) - ld c, #0xB8 - rst #0x08 - pop ix - ret - __endasm; -} - -void gfx_load_default_font(void) -{ - bios_get_zg(_gfx_font_buf); - _gfx_font_ptr = _gfx_font_buf; - _gfx_font_loaded = 1; -} - -void gfx_set_font(const uint8_t *font) -{ - _gfx_font_ptr = font; - _gfx_font_loaded = 1; -} - -/* Called from text renderers — loads the default font on first use. */ -void _gfx_font_ensure(void) -{ - if (!_gfx_font_loaded) gfx_load_default_font(); -} diff --git a/libc/gfx/gfx_get_bank.c b/libc/gfx/gfx_get_bank.c new file mode 100644 index 0000000..a7cbc79 --- /dev/null +++ b/libc/gfx/gfx_get_bank.c @@ -0,0 +1,10 @@ +/* + * gfx_get_bank — текущий байт страницы W3 (0x50..0x5F). + */ + +#include "_gfx.h" + +uint8_t gfx_get_bank(void) +{ + return _gfx_bank; +} diff --git a/libc/gfx/gfx_get_draw_page.c b/libc/gfx/gfx_get_draw_page.c new file mode 100644 index 0000000..a67e9a8 --- /dev/null +++ b/libc/gfx/gfx_get_draw_page.c @@ -0,0 +1,10 @@ +/* + * gfx_get_draw_page — кэшированный номер рисуемой страницы (0/1). + */ + +#include "_gfx.h" + +uint8_t gfx_get_draw_page(void) +{ + return _gfx_draw_page; +} diff --git a/libc/gfx/gfx_get_visible_page.c b/libc/gfx/gfx_get_visible_page.c new file mode 100644 index 0000000..e54fd4b --- /dev/null +++ b/libc/gfx/gfx_get_visible_page.c @@ -0,0 +1,10 @@ +/* + * gfx_get_visible_page — кэшированный номер видимой страницы (0/1). + */ + +#include "_gfx.h" + +uint8_t gfx_get_visible_page(void) +{ + return _gfx_visible_page; +} diff --git a/libc/gfx/gfx_hline16.c b/libc/gfx/gfx_hline16.c new file mode 100644 index 0000000..0e16f91 --- /dev/null +++ b/libc/gfx/gfx_hline16.c @@ -0,0 +1,12 @@ +/* + * gfx_hline16 — горизонтальная линия (mode 0x82) в W3-скобке. + */ + +#include "_gfx.h" + +void gfx_hline16(int x, int y, int len, uint8_t color) +{ + _gfx_w3_video_begin(); + _gfx_hline16_raw(x, y, len, color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_hline256.c b/libc/gfx/gfx_hline256.c new file mode 100644 index 0000000..36d9e20 --- /dev/null +++ b/libc/gfx/gfx_hline256.c @@ -0,0 +1,12 @@ +/* + * gfx_hline256 — горизонтальная линия (mode 0x81) в W3-скобке. + */ + +#include "_gfx.h" + +void gfx_hline256(int x, int y, int len, uint8_t color) +{ + _gfx_w3_video_begin(); + _gfx_hline256_raw(x, y, len, color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_init.c b/libc/gfx/gfx_init.c new file mode 100644 index 0000000..ad76c7c --- /dev/null +++ b/libc/gfx/gfx_init.c @@ -0,0 +1,19 @@ +/* + * gfx_init — переключиться в графический режим `mode`, вернуть прежний. + * + * `page` (0/1) — стартовая страница: и видимая, и рисуемая; банк W3 + * сбрасывается в 0x50 (канонический видимый видеобанк). Идёт через + * _videomode_raw_set — минуя text-only валидацию conio. + */ + +#include "_gfx.h" + +uint8_t gfx_init(uint8_t mode, uint8_t page) +{ + uint8_t prev = _videomode_raw_get(); + _videomode_raw_set(mode); + _gfx_bank = 0x50; + gfx_set_visible_page(page); + gfx_set_draw_page(page); + return prev; +} diff --git a/libc/gfx/gfx_line16.c b/libc/gfx/gfx_line16.c new file mode 100644 index 0000000..8620af2 --- /dev/null +++ b/libc/gfx/gfx_line16.c @@ -0,0 +1,35 @@ +/* + * gfx_line16 — линия (x0,y0)-(x1,y1) (mode 0x82). Ортогональные — + * через hline/vline; наклонные — Брезенхэм с одной W3-скобкой. + */ + +#include "_gfx.h" + +void gfx_line16(int x0, int y0, int x1, int y1, uint8_t color) +{ + if (y0 == y1) { + int x = x0 <= x1 ? x0 : x1; + int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1; + gfx_hline16(x, y0, w, color); + return; + } + if (x0 == x1) { + int y = y0 <= y1 ? y0 : y1; + int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1; + gfx_vline16(x0, y, h, color); + return; + } + int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx; + int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy; + int err = (dx > dy ? dx : -dy) / 2; + int x = x0, y = y0; + _gfx_w3_video_begin(); + for (;;) { + _gfx_putpixel16_raw(x, y, color); + if (x == x1 && y == y1) break; + int e2 = err; + if (e2 > -dx) { err -= dy; x += sx; } + if (e2 < dy) { err += dx; y += sy; } + } + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_line256.c b/libc/gfx/gfx_line256.c new file mode 100644 index 0000000..b3e0e1c --- /dev/null +++ b/libc/gfx/gfx_line256.c @@ -0,0 +1,37 @@ +/* + * gfx_line256 — линия (x0,y0)-(x1,y1) (mode 0x81). Ортогональные + * уходят на акселератор; наклонные — Брезенхэм с одной W3-скобкой + * вокруг всего цикла. + */ + +#include "_gfx.h" + +void gfx_line256(int x0, int y0, int x1, int y1, uint8_t color) +{ + if (y0 == y1) { + int x = x0 <= x1 ? x0 : x1; + int w = (x0 <= x1 ? x1 - x0 : x0 - x1) + 1; + gfx_hline256(x, y0, w, color); + return; + } + if (x0 == x1) { + int y = y0 <= y1 ? y0 : y1; + int h = (y0 <= y1 ? y1 - y0 : y0 - y1) + 1; + gfx_vline256(x0, y, h, color); + return; + } + + int dx = x1 - x0; int sx = dx < 0 ? -1 : 1; if (dx < 0) dx = -dx; + int dy = y1 - y0; int sy = dy < 0 ? -1 : 1; if (dy < 0) dy = -dy; + int err = (dx > dy ? dx : -dy) / 2; + int x = x0, y = y0; + _gfx_w3_video_begin(); + for (;;) { + _gfx_putpixel256_raw(x, y, color); + if (x == x1 && y == y1) break; + int e2 = err; + if (e2 > -dx) { err -= dy; x += sx; } + if (e2 < dy) { err += dx; y += sy; } + } + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_load_default_font.c b/libc/gfx/gfx_load_default_font.c new file mode 100644 index 0000000..5e28f3d --- /dev/null +++ b/libc/gfx/gfx_load_default_font.c @@ -0,0 +1,28 @@ +/* + * gfx_load_default_font — забрать активный системный знакогенератор + * через BIOS WIN_GET_ZG (fn 0xB8) в _gfx_font_buf и включить его. + */ + +#include "_gfx.h" + +/* BIOS WIN_GET_ZG (0xB8): DE = приёмник, возвращает 2 КБ. */ +static void bios_get_zg(uint8_t *dest) __naked +{ + (void)dest; + __asm + push ix ; BIOS клобберит IX + ex de, hl ; DE = dest (аргумент пришёл в HL) + xor a ; шрифт 0 (системный) + ld c, #0xB8 + rst #0x08 + pop ix + ret + __endasm; +} + +void gfx_load_default_font(void) +{ + bios_get_zg(_gfx_font_buf); + _gfx_font_ptr = _gfx_font_buf; + _gfx_font_loaded = 1; +} diff --git a/libc/gfx/gfx_pal_get.c b/libc/gfx/gfx_pal_get.c new file mode 100644 index 0000000..d9419e9 --- /dev/null +++ b/libc/gfx/gfx_pal_get.c @@ -0,0 +1,13 @@ +/* + * gfx_pal_get — прочитать блок записей графической палитры; + * обёртка над pal_get (libc/video) — не на горячем пути. + */ + +#include "_gfx.h" +#include + +void gfx_pal_get(uint8_t pal_num, uint8_t start, uint8_t count, + uint8_t *data) +{ + pal_get(pal_num, start, count, data); +} diff --git a/libc/gfx/gfx_pal_get_color.c b/libc/gfx/gfx_pal_get_color.c new file mode 100644 index 0000000..b958794 --- /dev/null +++ b/libc/gfx/gfx_pal_get_color.c @@ -0,0 +1,13 @@ +/* + * gfx_pal_get_color — прочитать одну запись графической палитры в + * R/G/B (любой указатель может быть NULL); обёртка над pal_get_color. + */ + +#include "_gfx.h" +#include + +void gfx_pal_get_color(uint8_t pal_num, uint8_t idx, + uint8_t *r, uint8_t *g, uint8_t *b) +{ + pal_get_color(pal_num, idx, r, g, b); +} diff --git a/libc/gfx/gfx_pal_load.c b/libc/gfx/gfx_pal_load.c new file mode 100644 index 0000000..d7e32d7 --- /dev/null +++ b/libc/gfx/gfx_pal_load.c @@ -0,0 +1,40 @@ +/* + * gfx_pal_load — загрузить блок записей графической палитры + * (BIOS $A4 PIC_SET_PAL): pal_num 0..3, start — первый слот, + * count слотов (0 = 256), data — записи по 4 байта (B, G, R, 0). + * + * Собственный inline-asm вместо тонкой обёртки над pal_load — тонкая + * обёртка через __sdcccall(1)-передачу эмпирически ломала текстовую + * палитру (следующая печать в текстовом режиме вешалась). + */ + +#include "_gfx.h" +#include + +static uint8_t gpl_num_; +static uint8_t gpl_start_; +static uint8_t gpl_count_; +static uint16_t gpl_data_; + +void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count, + const uint8_t *data) +{ + gpl_num_ = pal_num; + gpl_start_ = start; + gpl_count_ = count; + gpl_data_ = (uint16_t)(uintptr_t)data; + + __asm + push ix + ld a, (_gpl_start_) + ld e, a ; E = start + ld a, (_gpl_count_) + ld d, a ; D = count (0 = 256) + ld hl, (_gpl_data_) ; HL = data + ld b, #0xFF ; маска + ld a, (_gpl_num_) ; A = номер палитры + ld c, #0xA4 ; BIOS PIC_SET_PAL + rst #0x08 + pop ix + __endasm; +} diff --git a/libc/gfx/gfx_pal_reset.c b/libc/gfx/gfx_pal_reset.c new file mode 100644 index 0000000..67a52f0 --- /dev/null +++ b/libc/gfx/gfx_pal_reset.c @@ -0,0 +1,12 @@ +/* + * gfx_pal_reset — восстановить системную графическую палитру + * (BIOS $A6, type = PAL_GRAPH). + */ + +#include "_gfx.h" +#include + +void gfx_pal_reset(void) +{ + pal_reset(PAL_GRAPH); +} diff --git a/libc/gfx/gfx_pal_set.c b/libc/gfx/gfx_pal_set.c new file mode 100644 index 0000000..7546b3d --- /dev/null +++ b/libc/gfx/gfx_pal_set.c @@ -0,0 +1,17 @@ +/* + * gfx_pal_set — одна запись графической палитры из RGB: собирает + * четвёрку (B, G, R, 0) и зовёт gfx_pal_load(pal_num, idx, 1, ...). + */ + +#include "_gfx.h" + +void gfx_pal_set(uint8_t pal_num, uint8_t idx, + uint8_t r, uint8_t g, uint8_t b) +{ + uint8_t entry[4]; + entry[0] = b; + entry[1] = g; + entry[2] = r; + entry[3] = 0; + gfx_pal_load(pal_num, idx, 1, entry); +} diff --git a/libc/gfx/gfx_palette.c b/libc/gfx/gfx_palette.c deleted file mode 100644 index 2a5ca79..0000000 --- a/libc/gfx/gfx_palette.c +++ /dev/null @@ -1,74 +0,0 @@ -/* - * gfx_palette.c — graphics-side palette wrappers. - * - * The "legacy" entry points (gfx_pal_load / gfx_pal_set) keep their own - * inline-asm implementation rather than thunking through pal_*; this - * avoids depending on SDCC's __sdcccall(1) stack-shuffling for nested - * calls, which empirically misbehaves here (the trampoline corrupts the - * text palette so the next text-mode print hangs). Functionally these - * are identical to the old gfx_core.c versions. - * - * The newer get / reset helpers are simple wrappers — they're either - * not on hot paths or are first introduced here, so the thunk overhead - * doesn't matter and we get to share the asm with libc/video/palette.c. - */ - -#include -#include -#include - -static uint8_t gpl_num_; -static uint8_t gpl_start_; -static uint8_t gpl_count_; -static uint16_t gpl_data_; - -void gfx_pal_load(uint8_t pal_num, uint8_t start, uint8_t count, - const uint8_t *data) -{ - gpl_num_ = pal_num; - gpl_start_ = start; - gpl_count_ = count; - gpl_data_ = (uint16_t)(uintptr_t)data; - - __asm - push ix - ld a, (_gpl_start_) - ld e, a ; E = start - ld a, (_gpl_count_) - ld d, a ; D = count (0 → 256) - ld hl, (_gpl_data_) ; HL = data - ld b, #0xFF ; mask - ld a, (_gpl_num_) ; A = palette number - ld c, #0xA4 ; BIOS PIC_SET_PAL - rst #0x08 - pop ix - __endasm; -} - -void gfx_pal_set(uint8_t pal_num, uint8_t idx, - uint8_t r, uint8_t g, uint8_t b) -{ - uint8_t entry[4]; - entry[0] = b; - entry[1] = g; - entry[2] = r; - entry[3] = 0; - gfx_pal_load(pal_num, idx, 1, entry); -} - -void gfx_pal_get(uint8_t pal_num, uint8_t start, uint8_t count, - uint8_t *data) -{ - pal_get(pal_num, start, count, data); -} - -void gfx_pal_get_color(uint8_t pal_num, uint8_t idx, - uint8_t *r, uint8_t *g, uint8_t *b) -{ - pal_get_color(pal_num, idx, r, g, b); -} - -void gfx_pal_reset(void) -{ - pal_reset(PAL_GRAPH); -} diff --git a/libc/gfx/gfx_putchar16.c b/libc/gfx/gfx_putchar16.c new file mode 100644 index 0000000..fb78947 --- /dev/null +++ b/libc/gfx/gfx_putchar16.c @@ -0,0 +1,27 @@ +/* + * gfx_putchar16 — символ шрифтом 8×8 в (x,y), mode 0x82. + * x должен быть ЧЁТНЫМ (байтовое выравнивание); нечётный молча + * игнорируется — текстовые сетки обычно выровнены. + */ + +#include "_gfx.h" + +void gfx_putchar16(int x, int y, char c, uint8_t fg, uint8_t bg) +{ + _gfx_font_ensure(); + if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return; + if (x & 1) return; + + _gfx_t16_pair_build(fg, bg); + uint8_t cc = (uint8_t)c; + uint16_t base = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); + + for (int r = 0; r < 8; r++) { + int yy = y + r; + if ((unsigned)yy >= GFX_HEIGHT_16) break; + _gfx_t16_y = (uint8_t)yy; + _gfx_t16_addr = base; + _gfx_t16_row = _gfx_font_ptr[r * 256 + cc]; + _gfx_render_row16(); + } +} diff --git a/libc/gfx/gfx_text_256.c b/libc/gfx/gfx_putchar256.c similarity index 51% rename from libc/gfx/gfx_text_256.c rename to libc/gfx/gfx_putchar256.c index e08026b..dc53d14 100644 --- a/libc/gfx/gfx_text_256.c +++ b/libc/gfx/gfx_putchar256.c @@ -1,28 +1,19 @@ /* - * gfx_text_256.c — bitmap-font text rendering for mode 0x81 - * (320×256×256, one byte per pixel). - * - * Per glyph row (8 pixels): emit 8 bytes — FG where the source bit is - * 1, BG where 0. Port_Y is set once per row; the row writer wraps its - * own DI / W3 save+restore so callers don't need to. + * gfx_putchar256 — символ шрифтом 8×8 в (x,y), mode 0x81 (байт на + * пиксель). На ряд глифа (8 пикселей) пишем 8 байт: FG где бит 1, + * BG где 0. Рендер ряда сам делает DI / W3 save+restore — вызывать + * можно из любого контекста без скобок. */ -#include -#include +#include "_gfx.h" -extern uint16_t _gfx_addr_base; -extern const uint8_t *_gfx_font_ptr; -extern void _gfx_font_ensure(void); - -/* ---- Scratch shared with the asm row writer ---------------------- */ +/* Скретч рендера ряда — только этого модуля. */ static uint8_t txt_y; -static uint8_t txt_row; /* current glyph-row bit pattern */ +static uint8_t txt_row; /* битовый паттерн текущего ряда глифа */ static uint8_t txt_fg; static uint8_t txt_bg; -static uint16_t txt_addr; /* VRAM address for this row */ +static uint16_t txt_addr; /* VRAM-адрес ряда */ -/* Render one 8-pixel row. Self-contained DI / W3 save+restore so a - * gfx_text() can be called from any context without extra ceremony. */ static void render_row_256(void) __naked { __asm @@ -40,9 +31,9 @@ static void render_row_256(void) __naked ld a, (_txt_bg) ld e, a ld a, (_txt_row) - ld b, #8 ; 8 pixels in this row + ld b, #8 ; 8 пикселей в ряду rr256_loop: - rla ; CY = MSB, A <<= 1 + rla ; CY = старший бит, A <<= 1 jr nc, rr256_bg ld (hl), d ; FG jr rr256_next @@ -74,17 +65,8 @@ void gfx_putchar256(int x, int y, char c, uint8_t fg, uint8_t bg) if ((unsigned)yy >= GFX_HEIGHT) break; txt_y = (uint8_t)yy; txt_addr = base; - /* Interleaved layout: row r of char cc lives at font[r*256 + cc]. */ + /* Interleaved-раскладка: ряд r глифа cc — font[r*256 + cc]. */ txt_row = _gfx_font_ptr[r * 256 + cc]; render_row_256(); } } - -void gfx_text256(int x, int y, const char *s, uint8_t fg, uint8_t bg) -{ - for (; *s; s++) { - if (x >= GFX_WIDTH) break; - gfx_putchar256(x, y, *s, fg, bg); - x += 8; - } -} diff --git a/libc/gfx/gfx_putpixel16.c b/libc/gfx/gfx_putpixel16.c new file mode 100644 index 0000000..47294ef --- /dev/null +++ b/libc/gfx/gfx_putpixel16.c @@ -0,0 +1,14 @@ +/* + * gfx_putpixel16 — пиксель (x,y) цветом 0..15 (mode 0x82); + * одиночный вызов в собственной W3-скобке. + */ + +#include "_gfx.h" + +void gfx_putpixel16(int x, int y, uint8_t color) +{ + if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return; + _gfx_w3_video_begin(); + _gfx_putpixel16_raw(x, y, color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_putpixel256.c b/libc/gfx/gfx_putpixel256.c new file mode 100644 index 0000000..34e64b9 --- /dev/null +++ b/libc/gfx/gfx_putpixel256.c @@ -0,0 +1,14 @@ +/* + * gfx_putpixel256 — пиксель (x,y) цветом color (mode 0x81); + * одиночный вызов в собственной W3-скобке. + */ + +#include "_gfx.h" + +void gfx_putpixel256(int x, int y, uint8_t color) +{ + if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return; + _gfx_w3_video_begin(); + _gfx_putpixel256_raw(x, y, color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_raw_16.c b/libc/gfx/gfx_raw_16.c deleted file mode 100644 index 3b34763..0000000 --- a/libc/gfx/gfx_raw_16.c +++ /dev/null @@ -1,186 +0,0 @@ -/* - * gfx_raw_16.c — 16-colour (mode 0x82) raw primitives. - * - * Pixel format (verified empirically 2026-05-31): each byte at - * 0xC000+xb holds two horizontal pixels — - * high nibble (bits 7-4) = pixel at x = 2*xb (LEFT, even) - * low nibble (bits 3-0) = pixel at x = 2*xb + 1 (RIGHT, odd) - * - * Row addressing matches mode 0x81: Port_Y (0x89) = y, 320 bytes/row, - * 320 × 2 = 640 pixels. - * - * Accelerator: byte-wise — one Fill burst paints up to 256 bytes = - * 512 pixels. For a solid colour the byte value is the nibble in - * both halves: b = c | (c<<4). - * - * RMW pattern for unaligned edges & vertical lines: - * read byte at (HL); mask out target nibble; OR in new nibble; write. - * ~10 instructions per pixel — slow but unavoidable since a byte - * spans two horizontal pixels. - * - * "Raw" = W3-naive: caller wraps a sequence with one - * _gfx_w3_video_begin / _gfx_w3_video_end pair. - */ - -#include -#include - -extern uint16_t _gfx_addr_base; - -/* ---- Scratch shared across asm helpers ---------------------------- */ -static uint8_t g16_y; -static uint8_t g16_byte; /* combined byte: nibble | (nibble<<4) */ -static uint8_t g16_nibble; /* color in correct half (low or high) */ -static uint8_t g16_mask; /* mask preserving the OTHER half */ -static uint8_t g16_len; /* accel block size (0 = 256) */ -static uint16_t g16_addr; - -/* ---- Accel horizontal Fill burst ---------------------------------- * - * - * Caller has W3 mapped and DI active. Only the block-size byte uses - * SMC. Colour is preloaded into C and shipped via `ld a, c` (0x79). - * Inserting another `ld a, #n` between LD C,C and the firing LD (HL),A - * breaks the burst — the accel FSM re-reads the immediate as a fresh - * block size. - */ -static void g16_hfill_chunk(void) __naked -{ - __asm - ld a, (_g16_len) - ld (_g16_h_len_imm), a - - ld a, (_g16_byte) - ld c, a - - ld a, (_g16_y) - out (#0x89), a - - ld hl, (_g16_addr) - - ld d, d ; 0x52 — set block size - ld a, #0 ; 0x3E nn — length (patched) - _g16_h_len_imm = . - 1 - ld c, c ; 0x49 — horizontal Fill - ld a, c ; 0x79 — A = colour byte - ld (hl), a ; fires accel - ld b, b ; 0x40 — disable - ret - __endasm; -} - -/* ---- RMW one nibble at (g16_addr, g16_y) ------------------------- */ -static void g16_rmw_pixel(void) __naked -{ - __asm - ld a, (_g16_y) - out (#0x89), a - - ld hl, (_g16_addr) - ld a, (_g16_mask) - ld b, a ; B = preserve mask - ld a, (_g16_nibble) - ld c, a ; C = new nibble (in correct half) - ld a, (hl) - and a, b ; clear target nibble - or a, c ; OR in new - ld (hl), a - ret - __endasm; -} - -/* ---- Raw primitives (W3-naive, composable) ----------------------- */ - -void _gfx_putpixel16_raw(int x, int y, uint8_t color) -{ - if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return; - g16_y = (uint8_t)y; - g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); - if (x & 1) { - g16_nibble = (uint8_t)(color & 0x0F); - g16_mask = 0xF0; - } else { - g16_nibble = (uint8_t)((color & 0x0F) << 4); - g16_mask = 0x0F; - } - g16_rmw_pixel(); -} - -void _gfx_hline16_raw(int x, int y, int len, uint8_t color) -{ - if ((unsigned)y >= GFX_HEIGHT_16) return; - if (x < 0) { len += x; x = 0; } - if (x >= GFX_WIDTH_16) return; - if (x + len > GFX_WIDTH_16) len = GFX_WIDTH_16 - x; - if (len <= 0) return; - - g16_y = (uint8_t)y; - uint8_t cnib = color & 0x0F; - g16_byte = (uint8_t)(cnib | (cnib << 4)); - - /* Leading unaligned pixel: x odd → RIGHT half of left-most byte. */ - if (x & 1) { - g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); - g16_nibble = cnib; - g16_mask = 0xF0; - g16_rmw_pixel(); - x++; - len--; - if (len <= 0) return; - } - - /* Even x; emit (len/2) full bytes via accel hfill. */ - int full = len >> 1; - g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); - while (full > 0) { - int chunk = full > 256 ? 256 : full; - g16_len = (chunk == 256) ? 0 : (uint8_t)chunk; - g16_hfill_chunk(); - full -= chunk; - g16_addr += chunk; - } - - /* Trailing odd-length pixel: LEFT half of the next byte. */ - if (len & 1) { - g16_nibble = (uint8_t)(cnib << 4); - g16_mask = 0x0F; - g16_rmw_pixel(); - } -} - -void _gfx_vline16_raw(int x, int y, int len, uint8_t color) -{ - if ((unsigned)x >= GFX_WIDTH_16) return; - if (y < 0) { len += y; y = 0; } - if (y >= GFX_HEIGHT_16) return; - if (y + len > GFX_HEIGHT_16) len = GFX_HEIGHT_16 - y; - if (len <= 0) return; - - g16_addr = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); - if (x & 1) { - g16_nibble = (uint8_t)(color & 0x0F); - g16_mask = 0xF0; - } else { - g16_nibble = (uint8_t)((color & 0x0F) << 4); - g16_mask = 0x0F; - } - for (int i = 0; i < len; i++) { - g16_y = (uint8_t)(y + i); - g16_rmw_pixel(); - } -} - -/* Clear the whole 640×256 area with `color`. Row-major hfill — - * 256 rows × 2 bursts each (256+64 bytes). */ -void _gfx_clear16_raw(uint8_t color) -{ - g16_byte = (uint8_t)((color & 0x0F) | ((color & 0x0F) << 4)); - for (int y = 0; y < GFX_HEIGHT_16; y++) { - g16_y = (uint8_t)y; - g16_addr = _gfx_addr_base; - g16_len = 0; - g16_hfill_chunk(); - g16_addr = (uint16_t)(_gfx_addr_base + 0x100); - g16_len = 64; - g16_hfill_chunk(); - } -} diff --git a/libc/gfx/gfx_raw_256.c b/libc/gfx/gfx_raw_256.c deleted file mode 100644 index 2bdff0c..0000000 --- a/libc/gfx/gfx_raw_256.c +++ /dev/null @@ -1,175 +0,0 @@ -/* - * gfx_raw_256.c — 256-colour (mode 0x81) raw primitives. - * - * "Raw" = W3-naive: caller must have W3 mapped to the video bank and - * interrupts disabled (typically via _gfx_w3_video_begin/end from - * gfx_raw_common.c). Composite primitives in gfx_256.c wrap one - * begin/end around many raw calls so the W3 dance is amortised across - * the whole drawing operation. - * - * Addressing (mode 0x81): pixel (x, y) lives at CPU 0xC000 + x with - * Port_Y (0x89) = y. _gfx_addr_base is 0xC000 for draw page 0 and - * 0xC140 for draw page 1 — see gfx_core.c. - * - * Accelerator opcodes (docs/converted/accel_r.txt): - * LD D,D (0x52) enter "set block size" mode; the NEXT byte is the - * length operand and MUST follow LD A immediate (0x3E). - * LD C,C (0x49) horizontal Fill mode (LD (HL),A fills n bytes) - * LD E,E (0x5B) vertical Fill mode (auto-increments Port_Y) - * LD B,B (0x40) disable accelerator - * - * The block-length byte uses SMC — we patch the immediate at runtime, - * then run the accel sequence. HOME is RAM after DSS loads us, so - * SMC inside our own .EXE is safe. - */ - -#include -#include - -extern uint16_t _gfx_addr_base; - -/* ---- Scratch shared across asm helpers --------------------------- * - * Single-threaded — no reentrancy. GFX runs with interrupts off - * between _gfx_w3_video_begin and _gfx_w3_video_end. */ -static uint8_t acc_color; -static uint8_t acc_y; -static uint8_t acc_len; /* 0 means 256 — the accel convention */ -static uint16_t acc_addr; - -/* Putpixel scratch — separate from acc_* so a putpixel inside a - * Bresenham loop doesn't trample on outer accel state. */ -static uint8_t _gfx_pp_y; -static uint16_t _gfx_pp_addr; -static uint8_t _gfx_pp_color; - -/* ---- inner accel bursts ------------------------------------------ * - * - * Pre: W3 mapped to the video bank, DI active. Caller wraps a sequence - * of bursts in a single begin/end pair. */ - -/* Horizontal fill: acc_len bytes at acc_addr on row acc_y. */ -static void hfill_chunk(void) __naked -{ - __asm - ;; Patch the LD A,#n immediate (operand byte) with acc_len. - ld a, (_acc_len) - ld (_hfill_len_imm), a - - ld a, (_acc_y) - out (#0x89), a ; Port_Y = y - - ;; Pre-load colour into C and dest into HL before arming accel. - ld a, (_acc_color) - ld c, a - ld hl, (_acc_addr) - - ;; --- ACCEL SEQUENCE --- - ld d, d ; 0x52 — set block size mode - ld a, #0 ; 0x3E nn — block size (nn patched above) - _hfill_len_imm = . - 1 - ld c, c ; 0x49 — horizontal Fill - ld a, c ; 0x79 — A = colour (NOT another ld a,#n) - ld (hl), a ; fires accel; fills acc_len bytes - ld b, b ; 0x40 — disable - ret - __endasm; -} - -/* Vertical fill: acc_len pixels at column acc_addr, top row acc_y. - * The accel auto-increments Port_Y as it paints down the column. */ -static void vfill_chunk(void) __naked -{ - __asm - ld a, (_acc_len) - ld (_vfill_len_imm), a - - ld a, (_acc_y) - out (#0x89), a ; starting Y - - ld a, (_acc_color) - ld c, a - ld hl, (_acc_addr) - - ld d, d ; 0x52 — set block size - ld a, #0 ; immediate length (patched) - _vfill_len_imm = . - 1 - ld e, e ; 0x5B — vertical Fill - ld a, c ; A = colour - ld (hl), a ; fires accel - ld b, b ; 0x40 — disable - ret - __endasm; -} - -/* ---- Raw primitives (W3-naive, composable) ----------------------- * - * - * These do NO DI/W3 setup — caller wraps a sequence with one - * _gfx_w3_video_begin / _gfx_w3_video_end pair. */ - -void _gfx_putpixel256_raw(int x, int y, uint8_t color) -{ - if ((unsigned)x >= GFX_WIDTH || (unsigned)y >= GFX_HEIGHT) return; - _gfx_pp_y = (uint8_t)y; - _gfx_pp_addr = (uint16_t)(_gfx_addr_base + (unsigned)x); - _gfx_pp_color = color; - __asm - ld a, (__gfx_pp_y) - out (#0x89), a - ld hl, (__gfx_pp_addr) - ld a, (__gfx_pp_color) - ld (hl), a - __endasm; -} - -void _gfx_hline256_raw(int x, int y, int len, uint8_t color) -{ - if ((unsigned)y >= GFX_HEIGHT) return; - if (x < 0) { len += x; x = 0; } - if (x >= GFX_WIDTH) return; - if (x + len > GFX_WIDTH) len = GFX_WIDTH - x; - if (len <= 0) return; - - acc_color = color; - acc_y = (uint8_t)y; - acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x); - - while (len > 0) { - int chunk = len > 256 ? 256 : len; - acc_len = (chunk == 256) ? 0 : (uint8_t)chunk; - hfill_chunk(); - len -= chunk; - acc_addr += chunk; - } -} - -void _gfx_vline256_raw(int x, int y, int len, uint8_t color) -{ - if ((unsigned)x >= GFX_WIDTH) return; - if (y < 0) { len += y; y = 0; } - if (y >= GFX_HEIGHT) return; - if (y + len > GFX_HEIGHT) len = GFX_HEIGHT - y; - if (len <= 0) return; - - /* GFX_HEIGHT = 256 so a full column is a single accel burst. */ - acc_color = color; - acc_y = (uint8_t)y; - acc_addr = (uint16_t)(_gfx_addr_base + (unsigned)x); - acc_len = (len == 256) ? 0 : (uint8_t)len; - vfill_chunk(); -} - -/* Clear the whole 320×256 area with `color`. Row-major hfill: - * 256 rows × 2 bursts each (256-byte + 64-byte) = 512 bursts. */ -void _gfx_clear256_raw(uint8_t color) -{ - acc_color = color; - for (int y = 0; y < GFX_HEIGHT; y++) { - acc_y = (uint8_t)y; - acc_addr = _gfx_addr_base; /* 256-byte burst */ - acc_len = 0; - hfill_chunk(); - acc_addr = (uint16_t)(_gfx_addr_base + 256); /* 64-byte burst */ - acc_len = 64; - hfill_chunk(); - } -} diff --git a/libc/gfx/gfx_raw_common.c b/libc/gfx/gfx_raw_common.c deleted file mode 100644 index 2e860e7..0000000 --- a/libc/gfx/gfx_raw_common.c +++ /dev/null @@ -1,44 +0,0 @@ -/* - * gfx_raw_common.c — W3 page mapping primitives shared by every gfx mode. - * - * Composite primitives (gfx_line / gfx_rect / gfx_fill_rect / ...) bracket - * their inner loop with one `_gfx_w3_video_begin()` / `_gfx_w3_video_end()` - * pair and call the `*_raw` variants inside, so the W3 dance is paid once - * per drawing operation instead of once per pixel. Single-shot wrappers - * (gfx_putpixel, gfx_hline, ...) wrap the same way for their single call. - * - * Begin disables interrupts, saves the current W3 page byte, then maps - * `_gfx_bank` (the current video bank, 0x50..0x5F). End restores the - * saved page and re-enables interrupts. - * - * NOT re-entrant — saving the previous W3 byte in a static is safe only - * because GFX runs with interrupts off between begin and end. - */ - -#include - -extern uint8_t _gfx_bank; /* current W3 video bank (gfx_core.c) */ - -static uint8_t _gfx_saved_w3; - -void _gfx_w3_video_begin(void) __naked -{ - __asm - di - in a, (#0xE2) - ld (__gfx_saved_w3), a - ld a, (__gfx_bank) - out (#0xE2), a - ret - __endasm; -} - -void _gfx_w3_video_end(void) __naked -{ - __asm - ld a, (__gfx_saved_w3) - out (#0xE2), a - ei - ret - __endasm; -} diff --git a/libc/gfx/gfx_rect16.c b/libc/gfx/gfx_rect16.c new file mode 100644 index 0000000..7de60c7 --- /dev/null +++ b/libc/gfx/gfx_rect16.c @@ -0,0 +1,19 @@ +/* + * gfx_rect16 — рамка w×h от (x,y) (mode 0x82); все четыре стороны + * в одной W3-скобке. + */ + +#include "_gfx.h" + +void gfx_rect16(int x, int y, int w, int h, uint8_t color) +{ + if (w <= 0 || h <= 0) return; + _gfx_w3_video_begin(); + _gfx_hline16_raw(x, y, w, color); + _gfx_hline16_raw(x, y + h - 1, w, color); + if (h > 2) { + _gfx_vline16_raw(x, y + 1, h - 2, color); + _gfx_vline16_raw(x + w - 1, y + 1, h - 2, color); + } + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_rect256.c b/libc/gfx/gfx_rect256.c new file mode 100644 index 0000000..c2e3d47 --- /dev/null +++ b/libc/gfx/gfx_rect256.c @@ -0,0 +1,19 @@ +/* + * gfx_rect256 — рамка w×h от (x,y) (mode 0x81); все четыре стороны + * в одной W3-скобке. + */ + +#include "_gfx.h" + +void gfx_rect256(int x, int y, int w, int h, uint8_t color) +{ + if (w <= 0 || h <= 0) return; + _gfx_w3_video_begin(); + _gfx_hline256_raw(x, y, w, color); + _gfx_hline256_raw(x, y + h - 1, w, color); + if (h > 2) { + _gfx_vline256_raw(x, y + 1, h - 2, color); + _gfx_vline256_raw(x + w - 1, y + 1, h - 2, color); + } + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_set_bank.c b/libc/gfx/gfx_set_bank.c new file mode 100644 index 0000000..5c0ae44 --- /dev/null +++ b/libc/gfx/gfx_set_bank.c @@ -0,0 +1,10 @@ +/* + * gfx_set_bank — задать байт страницы W3 (0x50..0x5F) для gfx-записей. + */ + +#include "_gfx.h" + +void gfx_set_bank(uint8_t bank) +{ + _gfx_bank = bank; +} diff --git a/libc/gfx/gfx_set_draw_page.c b/libc/gfx/gfx_set_draw_page.c new file mode 100644 index 0000000..c7e10be --- /dev/null +++ b/libc/gfx/gfx_set_draw_page.c @@ -0,0 +1,14 @@ +/* + * gfx_set_draw_page — выбрать страницу для рисования (0/1); обновляет + * _gfx_addr_base, который читают все примитивы. + */ + +#include "_gfx.h" + +void gfx_set_draw_page(uint8_t page) +{ + _gfx_draw_page = page & 1; + /* Прямые константы короче (0xC000 + (cond ? 0x140 : 0)) на 3 + * инструкции Z80 — SDCC не сворачивает сложение констант. */ + _gfx_addr_base = _gfx_draw_page ? 0xC140 : 0xC000; +} diff --git a/libc/gfx/gfx_set_font.c b/libc/gfx/gfx_set_font.c new file mode 100644 index 0000000..f665bab --- /dev/null +++ b/libc/gfx/gfx_set_font.c @@ -0,0 +1,12 @@ +/* + * gfx_set_font — подменить шрифт своим (указатель удерживается — + * storage должен жить, пока шрифт активен). Формат см. _gfx.h. + */ + +#include "_gfx.h" + +void gfx_set_font(const uint8_t *font) +{ + _gfx_font_ptr = font; + _gfx_font_loaded = 1; +} diff --git a/libc/gfx/gfx_set_visible_page.c b/libc/gfx/gfx_set_visible_page.c new file mode 100644 index 0000000..c29c88a --- /dev/null +++ b/libc/gfx/gfx_set_visible_page.c @@ -0,0 +1,22 @@ +/* + * gfx_set_visible_page — выбрать видимую страницу (ESTEX $54 SELPAGE). + * + * Прямой OUT в порт 0xC9 бит 0 переключил бы регистр, но не уведомил + * бы display-учёт DSS — экран остаётся в рассогласованном состоянии + * (один из свопов показывается чёрным). Через syscall DSS доволен. + */ + +#include "_gfx.h" + +void gfx_set_visible_page(uint8_t page) +{ + _gfx_visible_page = page & 1; + __asm + push ix + ld a, (__gfx_visible_page) + ld b, a ; B = страница 0/1 + ld c, #0x54 ; ESTEX SELPAGE + rst #0x10 + pop ix + __endasm; +} diff --git a/libc/gfx/gfx_text16.c b/libc/gfx/gfx_text16.c new file mode 100644 index 0000000..6f14666 --- /dev/null +++ b/libc/gfx/gfx_text16.c @@ -0,0 +1,29 @@ +/* + * gfx_text16 — строка шрифтом 8×8 от (x,y), mode 0x82; x чётный, + * растёт на 8 на символ. Pair-таблица строится один раз на всю + * строку (FG/BG не меняются) — поэтому не через gfx_putchar16. + */ + +#include "_gfx.h" + +void gfx_text16(int x, int y, const char *s, uint8_t fg, uint8_t bg) +{ + _gfx_font_ensure(); + if (x & 1) return; + _gfx_t16_pair_build(fg, bg); + for (; *s; s++) { + if (x >= GFX_WIDTH_16) break; + if ((unsigned)y >= GFX_HEIGHT_16) break; + uint8_t cc = (uint8_t)*s; + uint16_t base = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); + for (int r = 0; r < 8; r++) { + int yy = y + r; + if ((unsigned)yy >= GFX_HEIGHT_16) break; + _gfx_t16_y = (uint8_t)yy; + _gfx_t16_addr = base; + _gfx_t16_row = _gfx_font_ptr[r * 256 + cc]; + _gfx_render_row16(); + } + x += 8; + } +} diff --git a/libc/gfx/gfx_text256.c b/libc/gfx/gfx_text256.c new file mode 100644 index 0000000..4ba8ee5 --- /dev/null +++ b/libc/gfx/gfx_text256.c @@ -0,0 +1,15 @@ +/* + * gfx_text256 — строка шрифтом 8×8 от (x,y), mode 0x81; x растёт + * на 8 на символ. + */ + +#include "_gfx.h" + +void gfx_text256(int x, int y, const char *s, uint8_t fg, uint8_t bg) +{ + for (; *s; s++) { + if (x >= GFX_WIDTH) break; + gfx_putchar256(x, y, *s, fg, bg); + x += 8; + } +} diff --git a/libc/gfx/gfx_text_16.c b/libc/gfx/gfx_text_16.c deleted file mode 100644 index 38ade86..0000000 --- a/libc/gfx/gfx_text_16.c +++ /dev/null @@ -1,124 +0,0 @@ -/* - * gfx_text_16.c — bitmap-font text rendering for mode 0x82 - * (640×256×16, 4 bits per pixel). - * - * Per glyph row (8 pixels): emit 4 bytes — each byte packs two - * adjacent pixels as (LEFT<<4) | RIGHT. A precomputed 4-entry pair - * table maps the 2-bit source pattern (LEFT*2 + RIGHT) directly to - * the output byte, avoiding per-pixel mask/OR. - * - * Currently requires byte-aligned x (x must be even); odd x is - * silently ignored. Text grids are typically aligned. - */ - -#include -#include - -extern uint16_t _gfx_addr_base; -extern const uint8_t *_gfx_font_ptr; -extern void _gfx_font_ensure(void); - -static uint8_t txt_y; -static uint8_t txt_row; -static uint16_t txt_addr; - -/* 4-byte lookup: index = LEFT*2 + RIGHT, value = (LEFT<<4)|RIGHT. */ -static uint8_t txt_pair[4]; - -/* Render one 8-pixel row using the pair table — 4 byte writes. */ -static void render_row_16(void) __naked -{ - __asm - di - in a, (#0xE2) - push af - ld a, #0x50 - out (#0xE2), a - ld a, (_txt_y) - out (#0x89), a - - ld hl, (_txt_addr) - ld a, (_txt_row) - ld c, a ; C = rotating source bits - ld b, #4 ; 4 output bytes - rr16_loop: - ;; Extract two top bits of C as index A = LEFT*2 + RIGHT. - sla c ; CY = LEFT bit; C <<= 1 - ld a, #0 - adc a, a ; A = LEFT - sla c ; CY = RIGHT bit; C <<= 1 - adc a, a ; A = LEFT*2 + RIGHT (0..3) - - ;; A = txt_pair[A]; preserve HL across the lookup. - push hl - ld e, a - ld d, #0 - ld hl, #_txt_pair - add hl, de - ld a, (hl) - pop hl - - ld (hl), a - inc hl - djnz rr16_loop - - pop af - out (#0xE2), a - ei - ret - __endasm; -} - -static void build_pair_table(uint8_t fg, uint8_t bg) -{ - uint8_t f = fg & 0x0F; - uint8_t b = bg & 0x0F; - /* HIGH nibble = LEFT, LOW nibble = RIGHT (sprinter_graphics convention). */ - txt_pair[0] = (uint8_t)((b << 4) | b); /* 00 BG BG */ - txt_pair[1] = (uint8_t)((b << 4) | f); /* 01 BG FG */ - txt_pair[2] = (uint8_t)((f << 4) | b); /* 10 FG BG */ - txt_pair[3] = (uint8_t)((f << 4) | f); /* 11 FG FG */ -} - -void gfx_putchar16(int x, int y, char c, uint8_t fg, uint8_t bg) -{ - _gfx_font_ensure(); - if ((unsigned)x >= GFX_WIDTH_16 || (unsigned)y >= GFX_HEIGHT_16) return; - if (x & 1) return; - - build_pair_table(fg, bg); - uint8_t cc = (uint8_t)c; - uint16_t base = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); - - for (int r = 0; r < 8; r++) { - int yy = y + r; - if ((unsigned)yy >= GFX_HEIGHT_16) break; - txt_y = (uint8_t)yy; - txt_addr = base; - txt_row = _gfx_font_ptr[r * 256 + cc]; - render_row_16(); - } -} - -void gfx_text16(int x, int y, const char *s, uint8_t fg, uint8_t bg) -{ - /* Build pair table once for the whole string (FG/BG don't change). */ - _gfx_font_ensure(); - if (x & 1) return; - build_pair_table(fg, bg); - for (; *s; s++) { - if (x >= GFX_WIDTH_16) break; - if ((unsigned)y >= GFX_HEIGHT_16) break; - uint8_t cc = (uint8_t)*s; - uint16_t base = (uint16_t)(_gfx_addr_base + ((unsigned)x >> 1)); - for (int r = 0; r < 8; r++) { - int yy = y + r; - if ((unsigned)yy >= GFX_HEIGHT_16) break; - txt_y = (uint8_t)yy; - txt_addr = base; - txt_row = _gfx_font_ptr[r * 256 + cc]; - render_row_16(); - } - x += 8; - } -} diff --git a/libc/gfx/gfx_vline16.c b/libc/gfx/gfx_vline16.c new file mode 100644 index 0000000..34406e8 --- /dev/null +++ b/libc/gfx/gfx_vline16.c @@ -0,0 +1,12 @@ +/* + * gfx_vline16 — вертикальная линия (mode 0x82) в W3-скобке. + */ + +#include "_gfx.h" + +void gfx_vline16(int x, int y, int len, uint8_t color) +{ + _gfx_w3_video_begin(); + _gfx_vline16_raw(x, y, len, color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_vline256.c b/libc/gfx/gfx_vline256.c new file mode 100644 index 0000000..594f00b --- /dev/null +++ b/libc/gfx/gfx_vline256.c @@ -0,0 +1,12 @@ +/* + * gfx_vline256 — вертикальная линия (mode 0x81) в W3-скобке. + */ + +#include "_gfx.h" + +void gfx_vline256(int x, int y, int len, uint8_t color) +{ + _gfx_w3_video_begin(); + _gfx_vline256_raw(x, y, len, color); + _gfx_w3_video_end(); +} diff --git a/libc/gfx/gfx_wait_vsync.c b/libc/gfx/gfx_wait_vsync.c new file mode 100644 index 0000000..97021fe --- /dev/null +++ b/libc/gfx/gfx_wait_vsync.c @@ -0,0 +1,18 @@ +/* + * gfx_wait_vsync — ждать следующий кадровый IM2-прерыв (50 Гц, DSS). + * + * HALT усыпляет Z80 до ближайшего IRQ; DSS обрабатывает его и + * возвращается на инструкцию после HALT — это начало обратного хода + * луча, идеальный момент для свопа страниц без разрывов. + */ + +#include "_gfx.h" + +void gfx_wait_vsync(void) __naked +{ + __asm + ei + halt + ret + __endasm; +} diff --git a/libc/include/stdlib.h b/libc/include/stdlib.h index ce8a295..398016c 100644 --- a/libc/include/stdlib.h +++ b/libc/include/stdlib.h @@ -1,6 +1,11 @@ #ifndef STDLIB_H #define STDLIB_H +/* Chain to SDCC's own stdlib.h (malloc/free/atoi/strtol/rand/qsort/…) — + * this header only ADDS Sprinter extensions and must not shadow the + * standard declarations. */ +#include_next + #include /* size_t */ #include diff --git a/libc/io/chdir.c b/libc/io/chdir.c new file mode 100644 index 0000000..641343e --- /dev/null +++ b/libc/io/chdir.c @@ -0,0 +1,24 @@ +/* + * chdir — сменить текущий каталог. ESTEX CHDIR ($1D), HL = путь. + * Возвращает 0 или -1 + errno. + */ + +#include + +int chdir(const char *path) __naked +{ + (void)path; + __asm + push ix + ld c, #0x1D ; ESTEX CHDIR; HL уже = путь + rst #0x10 + pop ix + jr c, _cd_err + ld de, #0 + ret + _cd_err: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/io/creat.c b/libc/io/creat.c new file mode 100644 index 0000000..28d8527 --- /dev/null +++ b/libc/io/creat.c @@ -0,0 +1,14 @@ +/* + * creat — создать/усечь файл на запись: open(path, O_WRONLY|O_CREAT| + * O_TRUNC). Параметр mode игнорируется — на Sprinter нет per-file + * прав доступа. + */ + +#include +#include + +int creat(const char *path, int mode) +{ + (void)mode; /* на Sprinter нет битов прав доступа */ + return open(path, O_WRONLY | O_CREAT | O_TRUNC); +} diff --git a/libc/io/dir.c b/libc/io/dir.c deleted file mode 100644 index 058d255..0000000 --- a/libc/io/dir.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * ffirst / fnext — directory iteration via ESTEX $19 / $1A. - * - * ESTEX F_FIRST ($19): - * HL = pattern, DE = buffer, A = attribute mask, B = format (0/1) - * CF = err / A = error code - * ESTEX F_NEXT ($1A): - * DE = same buffer - * CF = err / A = error code - * - * We always use format B=1 — 256-byte buffer with NUL-terminated DOS - * "name.ext" name at offset 33. - * - * ABI note: ffirst takes uint8_t as its 3rd arg. SDCC pushes a *single* - * byte for that (via `push af; inc sp`), not two — so the callee must - * pop ret-addr (2 bytes) AND consume the attr byte (`inc sp`) on the way - * out. Naively `pop bc` would over-eat into the caller's frame. - */ - -#include - -int ffirst(const char *pattern, ffblk_t *buf, uint8_t attrib) __naked -{ - (void)pattern; (void)buf; (void)attrib; - __asm - ;; On entry: HL = pattern, DE = buf, [SP+0..1] = ret, [SP+2] = attr. - ld iy, #2 - add iy, sp - ld a, 0 (iy) ; A = attr (read without disturbing SP) - - push ix - ld bc, #0x0119 ; ESTEX F_FIRST; format: 1 = DOS "name.ext" layout - rst #0x10 - pop ix - - pop hl ; HL = return address - inc sp ; consume the 1-byte attr - - jr c, _ff_err - ld de, #0 - jp (hl) - - _ff_err: - call __errno_set - ld de, #-1 - jp (hl) - __endasm; -} - -int fnext(ffblk_t *buf) __naked -{ - (void)buf; - __asm - ;; HL = buf on entry; ESTEX F_NEXT wants buf in DE. - push ix - ex de, hl - ld c, #0x1A ; ESTEX F_NEXT - rst #0x10 - pop ix - jr c, _fnext_err - ld de, #0 - ret - _fnext_err: - call __errno_set - ld de, #-1 - ret - __endasm; -} diff --git a/libc/io/ffirst.c b/libc/io/ffirst.c new file mode 100644 index 0000000..cb06034 --- /dev/null +++ b/libc/io/ffirst.c @@ -0,0 +1,43 @@ +/* + * ffirst — начать перебор каталога по шаблону через ESTEX F_FIRST ($19): + * HL = шаблон, DE = буфер, A = маска атрибутов, B = формат (0/1). + * CF = err / A = код ошибки. + * + * Всегда используем формат B=1 — 256-байтный буфер с ASCIIZ DOS-именем + * "name.ext" по смещению 33. Продолжение перебора — fnext() с тем же + * буфером. Возвращает 0 или -1 + errno. + * + * ABI: третий аргумент uint8_t SDCC пушит ОДНИМ байтом (push af; inc + * sp), поэтому callee снимает адрес возврата (2 байта) И съедает байт + * атрибута через `inc sp` — наивный `pop bc` переел бы кадр вызывающего. + */ + +#include + +int ffirst(const char *pattern, ffblk_t *buf, uint8_t attrib) __naked +{ + (void)pattern; (void)buf; (void)attrib; + __asm + ;; На входе: HL = шаблон, DE = буфер, [SP+0..1] = ret, [SP+2] = attr. + ld iy, #2 + add iy, sp + ld a, 0 (iy) ; A = attr (читаем, не трогая SP) + + push ix + ld bc, #0x0119 ; ESTEX F_FIRST; формат 1 = DOS "name.ext" + rst #0x10 + pop ix + + pop hl ; HL = адрес возврата + inc sp ; съесть 1-байтовый attr + + jr c, _ff_err + ld de, #0 + jp (hl) + + _ff_err: + call __errno_set + ld de, #-1 + jp (hl) + __endasm; +} diff --git a/libc/io/fnext.c b/libc/io/fnext.c new file mode 100644 index 0000000..7196ec8 --- /dev/null +++ b/libc/io/fnext.c @@ -0,0 +1,27 @@ +/* + * fnext — следующая запись перебора каталога, начатого ffirst(). + * ESTEX F_NEXT ($1A): DE = тот же буфер; CF = err / A = код ошибки. + * Возвращает 0 или -1 + errno (ENOENT-класс в конце перебора). + */ + +#include + +int fnext(ffblk_t *buf) __naked +{ + (void)buf; + __asm + ;; HL = buf на входе; ESTEX F_NEXT хочет буфер в DE. + push ix + ex de, hl + ld c, #0x1A ; ESTEX F_NEXT + rst #0x10 + pop ix + jr c, _fnext_err + ld de, #0 + ret + _fnext_err: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/io/fsdir.c b/libc/io/fsdir.c deleted file mode 100644 index b3edd15..0000000 --- a/libc/io/fsdir.c +++ /dev/null @@ -1,88 +0,0 @@ -/* - * fsdir.c — directory operations via ESTEX: - * $1B MKDIR — create directory (HL = path) - * $1C RMDIR — remove empty directory (HL = path) - * $1D CHDIR — change current directory (HL = path) - * $1E CURDIR — read current directory path (HL = 256-byte buffer) - * - * All four return CF=1 + A=error on failure. We surface that as errno - * with a -1 (or NULL for getcwd) return value, matching POSIX. - */ - -#include -#include - -int mkdir(const char *path) __naked -{ - (void)path; - __asm - push ix - ld c, #0x1B ; ESTEX MKDIR; HL already = path - rst #0x10 - pop ix - jr c, _mk_err - ld de, #0 - ret - _mk_err: - call __errno_set - ld de, #-1 - ret - __endasm; -} - -int rmdir(const char *path) __naked -{ - (void)path; - __asm - push ix - ld c, #0x1C ; ESTEX RMDIR; HL already = path - rst #0x10 - pop ix - jr c, _rm_err - ld de, #0 - ret - _rm_err: - call __errno_set - ld de, #-1 - ret - __endasm; -} - -int chdir(const char *path) __naked -{ - (void)path; - __asm - push ix - ld c, #0x1D ; ESTEX CHDIR; HL already = path - rst #0x10 - pop ix - jr c, _cd_err - ld de, #0 - ret - _cd_err: - call __errno_set - ld de, #-1 - ret - __endasm; -} - -char *getcwd(char *buf, size_t size) __naked -{ - (void)buf; (void)size; - __asm - ;; HL = buf, DE = size (ignored — ESTEX always wants 256 bytes). - push ix - push hl ; preserve buf across RST - ld c, #0x1E ; ESTEX CURDIR - rst #0x10 - pop hl ; restore buf - pop ix - jr c, _gc_err - ex de, hl ; return buf via DE (SDCC ptr return) - ret - _gc_err: - call __errno_set - ld de, #0 ; NULL on error - ret - __endasm; -} diff --git a/libc/io/fstat.c b/libc/io/fstat.c new file mode 100644 index 0000000..7fba3c1 --- /dev/null +++ b/libc/io/fstat.c @@ -0,0 +1,91 @@ +/* + * fstat — POSIX fstat() поверх метаданных ESTEX. + * + * fstat(fd, &st) -> ESTEX GET_D_T ($17) для mtime + lseek/SEEK_END + * для размера. + * + * Sprinter / DSS не хранит POSIX owner/group/inode, поэтому mode + * синтезируется минимальный (S_IFREG | rw для пользователя). + */ + +#include +#include +#include +#include +#include + +/* Дата/время файла по хендлу. ESTEX GET_D_T: A=fd, C=$17 → D=day, + * E=month, IX=year, H=hour, L=min, B=sec. CF=1 + A=errcode при ошибке. + * + * Пишет прямо в *out через `ex (sp), hl` (обмен сохранённого out с + * HL=час:мин после RST) — статический скретч не нужен. + * `out->dow` не трогается (GET_D_T его не возвращает). */ +static int get_dt_for_handle(int fd, datetime_t *out) __naked +{ + (void)fd; (void)out; + __asm + ;; __sdcccall(1): fd in HL (low byte), out in DE. + push ix ; save caller IX + push de ; stash out pointer + ld a, l ; A = fd + ld c, #0x17 ; ESTEX GET_D_T + rst #0x10 + jr c, _gdt_err + ;; D=day E=month IX=year H=hour L=min B=sec + ex (sp), hl ; TOS<->HL: HL=out, TOS=hour:min + ld (hl), d ; +0 day + inc hl + ld (hl), e ; +1 month + inc hl + push ix ; year onto stack + pop de ; DE = year + ld (hl), e ; +2 year low + inc hl + ld (hl), d ; +3 year high + inc hl + pop de ; D=hour E=min (from earlier ex (sp)) + ld (hl), d ; +4 hour + inc hl + ld (hl), e ; +5 min + inc hl + ld (hl), b ; +6 sec (+7 dow left untouched) + pop ix ; restore caller IX + ld de, #0 + ret + _gdt_err: + pop hl ; discard stashed out pointer + pop ix ; restore caller IX + call __errno_set + ld de, #-1 + ret + __endasm; +} + +int fstat(int fd, struct stat *buf) +{ + /* Размер — через lseek-трюк. */ + long cur = lseek(fd, 0L, SEEK_CUR); + if (cur < 0) return -1; + long end = lseek(fd, 0L, SEEK_END); + if (end < 0) return -1; + (void)lseek(fd, cur, SEEK_SET); + buf->st_size = (uint32_t)end; + + /* Дата/время — через ESTEX. */ + datetime_t ft; + if (get_dt_for_handle(fd, &ft) < 0) return -1; + { + struct tm tm; + tm.tm_sec = ft.second; + tm.tm_min = ft.minute; + tm.tm_hour = ft.hour; + tm.tm_mday = ft.day; + tm.tm_mon = (unsigned char)(ft.month - 1); + tm.tm_year = (int)ft.year - 1900; + tm.tm_isdst = 0; + tm.tm_hundredth = 0; + buf->st_mtime = mktime(&tm); + } + buf->st_mode = S_IFREG | S_IRUSR | S_IWUSR; + return 0; +} diff --git a/libc/io/getcwd.c b/libc/io/getcwd.c new file mode 100644 index 0000000..9ab7e7b --- /dev/null +++ b/libc/io/getcwd.c @@ -0,0 +1,28 @@ +/* + * getcwd — путь текущего каталога в buf. ESTEX CURDIR ($1E) пишет в + * буфер (ESTEX всегда рассчитывает на 256 байт — параметр size + * игнорируется). Возвращает buf или NULL + errno. + */ + +#include + +char *getcwd(char *buf, size_t size) __naked +{ + (void)buf; (void)size; + __asm + ;; HL = buf, DE = size (игнорируется — ESTEX хочет 256 байт). + push ix + push hl ; сохранить buf через RST + ld c, #0x1E ; ESTEX CURDIR + rst #0x10 + pop hl ; вернуть buf + pop ix + jr c, _gc_err + ex de, hl ; вернуть buf в DE (SDCC ptr return) + ret + _gc_err: + call __errno_set + ld de, #0 ; NULL при ошибке + ret + __endasm; +} diff --git a/libc/io/lseek.c b/libc/io/lseek.c index 5328dfc..4fe83ea 100644 --- a/libc/io/lseek.c +++ b/libc/io/lseek.c @@ -1,18 +1,18 @@ /* - * lseek — 32-bit file position via ESTEX MOVE_FP ($15). + * lseek — 32-битная позиция файла через ESTEX MOVE_FP ($15). * * ESTEX MOVE_FP: A=handle, B=whence (0=SET, 1=CUR, 2=END), * HL=offset high16, IX=offset low16 - * → HL:IX = new absolute position, CF=err + * возврат: HL:IX = новая абсолютная позиция, CF=err * - * SDCC __sdcccall(1) on z80 for `long lseek(int fd, long offset, int whence)`: - * - fd → HL (1st 16-bit arg in register) - * - offset → stack as two 16-bit words (low first, then high) - * - whence → stack (top after offset) - * - 32-bit return: DE = low16, HL = high16 - * - caller-pops (caller-side `pop af; pop af; pop af` after the call) + * SDCC __sdcccall(1) на z80 для `long lseek(int fd, long offset, int whence)`: + * - fd — HL (первый 16-битный аргумент в регистре) + * - offset — на стеке двумя 16-битными словами (сначала low, потом high) + * - whence — на стеке (выше offset) + * - 32-битный возврат: DE = low16, HL = high16 + * - стек чистит вызывающий (`pop af; pop af; pop af` после call) * - * IX is saved (caller frame pointer). + * IX сохраняется (frame pointer вызывающего). */ #include @@ -21,21 +21,21 @@ long lseek(int fd, long offset, int whence) __naked { (void)fd; (void)offset; (void)whence; __asm - push ix ; save caller-side IX - ;; Layout after push: - ;; SP+0..1 = saved IX - ;; SP+2..3 = ret addr + push ix ; сохранить IX вызывающего + ;; Раскладка после push: + ;; SP+0..1 = сохранённый IX + ;; SP+2..3 = адрес возврата ;; SP+4..5 = offset low16 ;; SP+6..7 = offset high16 ;; SP+8..9 = whence - ld a, l ; A = fd low byte (was in HL) + ld a, l ; A = младший байт fd (был в HL) - ;; Walk through 5 consecutive stack bytes via HL — cheaper than - ;; IY-indexed because `ld r,(hl); inc hl` (2B/13T per byte) beats - ;; `ld r, n(iy)` (3B/19T) for sequential reads. + ;; 5 последовательных байтов стека читаем через HL — дешевле, + ;; чем IY-индексация: `ld r,(hl); inc hl` (2Б/13Т на байт) + ;; против `ld r, n(iy)` (3Б/19Т) при последовательном чтении. ld hl, #4 - add hl, sp ; HL → offset_low + add hl, sp ; HL указывает на offset_low ld e, (hl) inc hl @@ -47,18 +47,19 @@ long lseek(int fd, long offset, int whence) __naked inc hl ld d, (hl) ; DE = offset_high inc hl - ld b, (hl) ; B = whence (low byte) - ex de, hl ; HL = offset_high (ESTEX wants it here) + ld b, (hl) ; B = whence (младший байт) + ex de, hl ; HL = offset_high (ESTEX ждёт его тут) ld c, #0x15 ; ESTEX MOVE_FP rst #0x10 jr c, _lseek_err - ;; Returns HL:IX = new position. Convert to SDCC long return (DE:HL). + ;; Вернулось HL:IX = новая позиция. Приводим к long-возврату + ;; SDCC (DE:HL). push ix - pop de ; DE = low16 (was IX) - ;; HL already has high16 - pop ix ; restore caller-side IX + pop de ; DE = low16 (был IX) + ;; HL уже содержит high16 + pop ix ; восстановить IX вызывающего ret _lseek_err: diff --git a/libc/io/mkdir.c b/libc/io/mkdir.c new file mode 100644 index 0000000..6662845 --- /dev/null +++ b/libc/io/mkdir.c @@ -0,0 +1,24 @@ +/* + * mkdir — создать каталог. ESTEX MKDIR ($1B), HL = путь. + * Возвращает 0 или -1 + errno. + */ + +#include + +int mkdir(const char *path) __naked +{ + (void)path; + __asm + push ix + ld c, #0x1B ; ESTEX MKDIR; HL уже = путь + rst #0x10 + pop ix + jr c, _mk_err + ld de, #0 + ret + _mk_err: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/io/read.c b/libc/io/read.c index 85d2dd2..2553aee 100644 --- a/libc/io/read.c +++ b/libc/io/read.c @@ -1,15 +1,12 @@ /* - * read / write — bulk transfer through ESTEX file handles. + * read — блочное чтение из файлового хендла ESTEX. * - * ESTEX READ ($13) / WRITE ($14): - * A = handle, HL = buffer, DE = byte count - * → DE = bytes actually transferred, CF = err with code in A. + * ESTEX READ ($13): A=handle, HL=буфер, DE=число байт + * → DE = фактически прочитано, CF=err с кодом в A. * - * SDCC __sdcccall(1) for 3-arg int functions uses callee-pops for the - * stack-passed argument; this implementation mirrors the pattern used - * by SDCC's own z80.lib _memset. - * - * On error: sets errno, returns -1. + * SDCC __sdcccall(1) для 3-аргументных int-функций: третий аргумент на + * стеке снимает callee (паттерн как в _memset из SDCC z80.lib). + * При ошибке: errno установлен, возврат -1. */ #include @@ -18,21 +15,21 @@ int read(int fd, void *buf, size_t n) __naked { (void)fd; (void)buf; (void)n; __asm - pop iy ; IY = return address - pop bc ; BC = n (stack arg) + pop iy ; IY = адрес возврата + pop bc ; BC = n (стековый аргумент) ld a, l ; A = handle ex de, hl ; HL = buf ld d, b ld e, c ; DE = n push ix - push iy ; preserve return addr across RST + push iy ; сохранить адрес возврата через RST ld c, #0x13 ; ESTEX READ rst #0x10 pop iy pop ix jr c, _read_err - ;; DE already holds count read. + ;; DE уже держит прочитанное количество. jp (iy) _read_err: call __errno_set @@ -40,28 +37,3 @@ int read(int fd, void *buf, size_t n) __naked jp (iy) __endasm; } - -int write(int fd, const void *buf, size_t n) __naked -{ - (void)fd; (void)buf; (void)n; - __asm - pop iy - pop bc - ld a, l - ex de, hl - ld d, b - ld e, c - push ix - push iy - ld c, #0x14 - rst #0x10 - pop iy - pop ix - jr c, _write_err - jp (iy) - _write_err: - call __errno_set - ld de, #-1 - jp (iy) - __endasm; -} diff --git a/libc/io/rmdir.c b/libc/io/rmdir.c new file mode 100644 index 0000000..e6b9eb5 --- /dev/null +++ b/libc/io/rmdir.c @@ -0,0 +1,24 @@ +/* + * rmdir — удалить ПУСТОЙ каталог. ESTEX RMDIR ($1C), HL = путь. + * Возвращает 0 или -1 + errno. + */ + +#include + +int rmdir(const char *path) __naked +{ + (void)path; + __asm + push ix + ld c, #0x1C ; ESTEX RMDIR; HL уже = путь + rst #0x10 + pop ix + jr c, _rm_err + ld de, #0 + ret + _rm_err: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/io/stat.c b/libc/io/stat.c index 83b083b..bb68fa8 100644 --- a/libc/io/stat.c +++ b/libc/io/stat.c @@ -1,15 +1,9 @@ /* - * stat.c — POSIX stat() and fstat() over ESTEX metadata. + * stat — POSIX stat() поверх метаданных ESTEX. * - * fstat(fd, &st) -> ESTEX GET_D_T ($17) for mtime + lseek/SEEK_END - * for size. - * stat(path, &st) -> open(O_RDONLY) + fstat() + close. This works - * for any regular file; directory paths fail at - * open() — F_FIRST-based stat had unreliable - * semantics for exact filenames. - * - * Sprinter / DSS doesn't track POSIX owner/group/inode, so we synth a - * minimal mode (S_IFREG | rw user perm). + * stat(path, &st) -> open(O_RDONLY) + fstat() + close. Работает для + * любого обычного файла; для директорий — fallback + * через F_FIRST (и перебор "*.*" для "."/".."). */ #include @@ -21,84 +15,8 @@ #include #include -/* ESTEX GET_D_T: A=fd, C=$17 → D=day, E=month, IX=year, H=hour, L=min, - * B=sec. CF=1 + A=errcode on failure. - * - * Writes directly into *out using `ex (sp), hl` to swap the saved out - * pointer with HL=hour:min after RST — no static scratch needed. - * `out->dow` is left untouched (GET_D_T doesn't return it). */ -static int get_dt_for_handle(int fd, datetime_t *out) __naked -{ - (void)fd; (void)out; - __asm - ;; __sdcccall(1): fd in HL (low byte), out in DE. - push ix ; save caller IX - push de ; stash out pointer - ld a, l ; A = fd - ld c, #0x17 ; ESTEX GET_D_T - rst #0x10 - jr c, _gdt_err - ;; D=day E=month IX=year H=hour L=min B=sec - ex (sp), hl ; TOS<->HL: HL=out, TOS=hour:min - ld (hl), d ; +0 day - inc hl - ld (hl), e ; +1 month - inc hl - push ix ; year onto stack - pop de ; DE = year - ld (hl), e ; +2 year low - inc hl - ld (hl), d ; +3 year high - inc hl - pop de ; D=hour E=min (from earlier ex (sp)) - ld (hl), d ; +4 hour - inc hl - ld (hl), e ; +5 min - inc hl - ld (hl), b ; +6 sec (+7 dow left untouched) - pop ix ; restore caller IX - ld de, #0 - ret - _gdt_err: - pop hl ; discard stashed out pointer - pop ix ; restore caller IX - call __errno_set - ld de, #-1 - ret - __endasm; -} - -int fstat(int fd, struct stat *buf) -{ - /* Size via lseek trick. */ - long cur = lseek(fd, 0L, SEEK_CUR); - if (cur < 0) return -1; - long end = lseek(fd, 0L, SEEK_END); - if (end < 0) return -1; - (void)lseek(fd, cur, SEEK_SET); - buf->st_size = (uint32_t)end; - - /* Date/time via ESTEX. */ - datetime_t ft; - if (get_dt_for_handle(fd, &ft) < 0) return -1; - { - struct tm tm; - tm.tm_sec = ft.second; - tm.tm_min = ft.minute; - tm.tm_hour = ft.hour; - tm.tm_mday = ft.day; - tm.tm_mon = (unsigned char)(ft.month - 1); - tm.tm_year = (int)ft.year - 1900; - tm.tm_isdst = 0; - tm.tm_hundredth = 0; - buf->st_mtime = mktime(&tm); - } - buf->st_mode = S_IFREG | S_IRUSR | S_IWUSR; - return 0; -} - -/* Convert ESTEX DOS-style date+time to time_t epoch (used by stat() - * when going through F_FIRST for directory entries). */ +/* Конвертация DOS-формата даты+времени ESTEX в time_t (используется + * на пути через F_FIRST для записей директорий). */ static time_t dos_to_epoch(uint16_t date, uint16_t dtime) { struct tm tm; @@ -113,8 +31,8 @@ static time_t dos_to_epoch(uint16_t date, uint16_t dtime) return mktime(&tm); } -/* Returns 1 if path is "." or "..", else 0. Reads at most 3 bytes; - * NULL-safe. ~28 bytes / 34–117 T-states depending on input. */ +/* Возвращает 1, если path — "." или "..", иначе 0. Читает максимум + * 3 байта; NULL-безопасно. ~28 байт / 34–117 тактов от входа. */ static char is_dot_or_dotdot(const char *path) __naked { (void)path; @@ -146,7 +64,7 @@ static char is_dot_or_dotdot(const char *path) __naked int stat(const char *path, struct stat *buf) { - /* Regular file: open + fstat. */ + /* Обычный файл: open + fstat. */ int fd = open(path, O_RDONLY); if (fd >= 0) { int r = fstat(fd, buf); @@ -155,7 +73,7 @@ int stat(const char *path, struct stat *buf) } int saved = errno; - /* Try ffirst directly — works for ordinary subdirectories. */ + /* Пробуем ffirst напрямую — работает для обычных подпапок. */ ffblk_t ffb; if (ffirst(path, &ffb, FA_DIREC) == 0 && (ffb.found_attr & FA_DIREC)) { buf->st_size = ffb.size; @@ -164,9 +82,9 @@ int stat(const char *path, struct stat *buf) return 0; } - /* Verified 2026-05-29: ESTEX F_FIRST rejects bare "." and ".." with - * EINAME (16), same as open(). But they DO appear in the "*.*" - * directory listing with FA_DIREC. Iterate to find them. */ + /* Проверено 2026-05-29: ESTEX F_FIRST отвергает голые "." и ".." + * с EINAME (16), как и open(). Но в листинге "*.*" они ЕСТЬ с + * FA_DIREC — ищем перебором. */ if (is_dot_or_dotdot(path)) { if (ffirst("*.*", &ffb, FA_DIREC) == 0) { do { @@ -178,7 +96,7 @@ int stat(const char *path, struct stat *buf) } } while (fnext(&ffb) == 0); } - /* Last-resort synthetic entry — FS variant didn't expose them. */ + /* Крайний случай — синтетическая запись: ФС их не отдала. */ buf->st_mode = S_IFDIR | S_IRWXU; buf->st_size = 0; buf->st_mtime = 0; diff --git a/libc/io/unlink.c b/libc/io/unlink.c index 0066a1e..0eda3c0 100644 --- a/libc/io/unlink.c +++ b/libc/io/unlink.c @@ -1,6 +1,6 @@ /* - * unlink — remove a file via ESTEX DELETE ($0E). - * HL = ASCIIZ path; CF=err with code in A. Sets errno on failure. + * unlink — удалить файл через ESTEX DELETE ($0E). + * HL = ASCIIZ-путь; CF=err с кодом в A. При ошибке ставит errno. */ #include @@ -10,7 +10,7 @@ int unlink(const char *path) __naked (void)path; __asm push ix - ld c, #0x0E ; ESTEX DELETE (HL = file name, A = attribure file) + ld c, #0x0E ; ESTEX DELETE (HL = имя файла, A = атрибут) rst #0x10 pop ix jr c, _unlink_err diff --git a/libc/mem/bank_io_w1.c b/libc/mem/bank_io_w1.c deleted file mode 100644 index f40760a..0000000 --- a/libc/mem/bank_io_w1.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * bank_io_w1.c — far-page accessors that swap window 1 (port 0xA2, - * base 0x4000). Mirror of bank_io.c but through W1. - * - * SAFE ONLY in `--memory tiny` builds. In tiny mode all code and data - * fit in W2, so both W1 and W3 are free for arbitrary banked data. - * - * small / huge: code lives in W1 (HOME) → swapping W1 unmaps the - * function mid-call and crashes. - * big : banked code segment lives in W1 → swap clobbers it. - * - * Split into its own .rel so that programs using only the W3 variants - * do NOT pull these functions in (and vice versa). - */ - -#include -#include -#include -#include - -uint8_t bank_load_byte_w1(uint8_t phys_page, uint16_t off_in_window) -{ - uint8_t saved = _io_page_w1; - sprinter_page_w1(phys_page); - uint8_t v = *((volatile uint8_t *)(0x4000u + off_in_window)); - sprinter_page_w1(saved); - return v; -} - -void bank_store_byte_w1(uint8_t phys_page, uint16_t off_in_window, uint8_t v) -{ - uint8_t saved = _io_page_w1; - sprinter_page_w1(phys_page); - *((volatile uint8_t *)(0x4000u + off_in_window)) = v; - sprinter_page_w1(saved); -} - -void bank_read_w1(uint8_t phys_page, uint16_t off, void *dst, uint16_t n) -{ - uint8_t saved = _io_page_w1; - sprinter_page_w1(phys_page); - memcpy(dst, (const void *)(0x4000u + off), n); - sprinter_page_w1(saved); -} - -void bank_write_w1(uint8_t phys_page, uint16_t off, const void *src, uint16_t n) -{ - uint8_t saved = _io_page_w1; - sprinter_page_w1(phys_page); - memcpy((void *)(0x4000u + off), src, n); - sprinter_page_w1(saved); -} diff --git a/libc/mem/bank_io_w3.c b/libc/mem/bank_io_w3.c deleted file mode 100644 index 2c6292d..0000000 --- a/libc/mem/bank_io_w3.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * bank_io_w3.c — far-page accessors that swap window 3 (port 0xE2, - * base 0xC000). Save the current W3 page, set the target, do the - * byte/memcpy access, restore the saved page. - * - * Safe in `tiny`, `small`, `big` memory modes (W3 is free for data). - * NOT safe in `huge` — there banked code lives in W3. - * - * No DI/EI around the swap: ISRs that don't touch W3 are unaffected; - * concurrent W3 use from an ISR is unsafe. - * - * The `_w1` family (bank_io_w1.c) is the mirror through W1 and is - * `tiny`-only. See sprinter_mem.h for the full safety matrix. - */ - -#include -#include -#include -#include - -uint8_t bank_load_byte(uint8_t phys_page, uint16_t off_in_window) -{ - uint8_t saved = _io_page_w3; - sprinter_page_w3(phys_page); - uint8_t v = *((volatile uint8_t *)(0xC000u + off_in_window)); - sprinter_page_w3(saved); - return v; -} - -void bank_store_byte(uint8_t phys_page, uint16_t off_in_window, uint8_t v) -{ - uint8_t saved = _io_page_w3; - sprinter_page_w3(phys_page); - *((volatile uint8_t *)(0xC000u + off_in_window)) = v; - sprinter_page_w3(saved); -} - -void bank_read(uint8_t phys_page, uint16_t off, void *dst, uint16_t n) -{ - uint8_t saved = _io_page_w3; - sprinter_page_w3(phys_page); - memcpy(dst, (const void *)(0xC000u + off), n); - sprinter_page_w3(saved); -} - -void bank_write(uint8_t phys_page, uint16_t off, const void *src, uint16_t n) -{ - uint8_t saved = _io_page_w3; - sprinter_page_w3(phys_page); - memcpy((void *)(0xC000u + off), src, n); - sprinter_page_w3(saved); -} diff --git a/libc/mem/bank_load_byte.c b/libc/mem/bank_load_byte.c new file mode 100644 index 0000000..27d36e5 --- /dev/null +++ b/libc/mem/bank_load_byte.c @@ -0,0 +1,23 @@ +/* + * bank_load_byte — прочитать байт из чужой физической страницы через + * окно 3 (порт 0xE2, база 0xC000): сохранить текущую страницу W3, + * подставить целевую, прочитать, вернуть страницу назад. + * + * Безопасно в режимах tiny/small/big (W3 свободно под данные), + * НЕ безопасно в huge (там в W3 живёт banked-код). DI/EI вокруг свопа + * нет — конкурентное использование W3 из ISR небезопасно. + * Семейство *_w1 (через окно 1) — только для tiny; см. sprinter_mem.h. + */ + +#include +#include +#include + +uint8_t bank_load_byte(uint8_t phys_page, uint16_t off_in_window) +{ + uint8_t saved = _io_page_w3; + sprinter_page_w3(phys_page); + uint8_t v = *((volatile uint8_t *)(0xC000u + off_in_window)); + sprinter_page_w3(saved); + return v; +} diff --git a/libc/mem/bank_load_byte_w1.c b/libc/mem/bank_load_byte_w1.c new file mode 100644 index 0000000..44c0a5e --- /dev/null +++ b/libc/mem/bank_load_byte_w1.c @@ -0,0 +1,22 @@ +/* + * bank_load_byte_w1 — прочитать байт из чужой физической страницы через + * окно 1 (порт 0xA2, база 0x4000) со свопом-возвратом страницы W1. + * + * ТОЛЬКО для `--memory tiny`: там весь код/данные в W2, и W1 свободно. + * small/huge: в W1 живёт код (HOME) → своп размапит функцию на лету; + * big: в W1 живёт banked-код → своп его затирает. + * Зеркало W3-семейства; см. sprinter_mem.h (матрица безопасности). + */ + +#include +#include +#include + +uint8_t bank_load_byte_w1(uint8_t phys_page, uint16_t off_in_window) +{ + uint8_t saved = _io_page_w1; + sprinter_page_w1(phys_page); + uint8_t v = *((volatile uint8_t *)(0x4000u + off_in_window)); + sprinter_page_w1(saved); + return v; +} diff --git a/libc/mem/bank_read.c b/libc/mem/bank_read.c new file mode 100644 index 0000000..bfbd03b --- /dev/null +++ b/libc/mem/bank_read.c @@ -0,0 +1,18 @@ +/* + * bank_read — скопировать n байт из чужой физической страницы (окно 3, + * порт 0xE2, база 0xC000) в dst через memcpy со свопом-возвратом W3. + * Безопасность по режимам — как у bank_load_byte (см. sprinter_mem.h). + */ + +#include +#include +#include +#include + +void bank_read(uint8_t phys_page, uint16_t off, void *dst, uint16_t n) +{ + uint8_t saved = _io_page_w3; + sprinter_page_w3(phys_page); + memcpy(dst, (const void *)(0xC000u + off), n); + sprinter_page_w3(saved); +} diff --git a/libc/mem/bank_read_w1.c b/libc/mem/bank_read_w1.c new file mode 100644 index 0000000..c29f350 --- /dev/null +++ b/libc/mem/bank_read_w1.c @@ -0,0 +1,18 @@ +/* + * bank_read_w1 — скопировать n байт из чужой физической страницы + * (окно 1, порт 0xA2, база 0x4000) в dst со свопом-возвратом W1. + * ТОЛЬКО для `--memory tiny` — см. bank_load_byte_w1.c. + */ + +#include +#include +#include +#include + +void bank_read_w1(uint8_t phys_page, uint16_t off, void *dst, uint16_t n) +{ + uint8_t saved = _io_page_w1; + sprinter_page_w1(phys_page); + memcpy(dst, (const void *)(0x4000u + off), n); + sprinter_page_w1(saved); +} diff --git a/libc/mem/bank_store_byte.c b/libc/mem/bank_store_byte.c new file mode 100644 index 0000000..7637d99 --- /dev/null +++ b/libc/mem/bank_store_byte.c @@ -0,0 +1,17 @@ +/* + * bank_store_byte — записать байт в чужую физическую страницу через + * окно 3 (порт 0xE2, база 0xC000) со свопом-возвратом страницы W3. + * Безопасность по режимам — как у bank_load_byte (см. sprinter_mem.h). + */ + +#include +#include +#include + +void bank_store_byte(uint8_t phys_page, uint16_t off_in_window, uint8_t v) +{ + uint8_t saved = _io_page_w3; + sprinter_page_w3(phys_page); + *((volatile uint8_t *)(0xC000u + off_in_window)) = v; + sprinter_page_w3(saved); +} diff --git a/libc/mem/bank_store_byte_w1.c b/libc/mem/bank_store_byte_w1.c new file mode 100644 index 0000000..f489522 --- /dev/null +++ b/libc/mem/bank_store_byte_w1.c @@ -0,0 +1,17 @@ +/* + * bank_store_byte_w1 — записать байт в чужую физическую страницу через + * окно 1 (порт 0xA2, база 0x4000) со свопом-возвратом W1. + * ТОЛЬКО для `--memory tiny` — см. bank_load_byte_w1.c. + */ + +#include +#include +#include + +void bank_store_byte_w1(uint8_t phys_page, uint16_t off_in_window, uint8_t v) +{ + uint8_t saved = _io_page_w1; + sprinter_page_w1(phys_page); + *((volatile uint8_t *)(0x4000u + off_in_window)) = v; + sprinter_page_w1(saved); +} diff --git a/libc/mem/bank_write.c b/libc/mem/bank_write.c new file mode 100644 index 0000000..334ac43 --- /dev/null +++ b/libc/mem/bank_write.c @@ -0,0 +1,18 @@ +/* + * bank_write — скопировать n байт из src в чужую физическую страницу + * (окно 3, порт 0xE2, база 0xC000) через memcpy со свопом-возвратом W3. + * Безопасность по режимам — как у bank_load_byte (см. sprinter_mem.h). + */ + +#include +#include +#include +#include + +void bank_write(uint8_t phys_page, uint16_t off, const void *src, uint16_t n) +{ + uint8_t saved = _io_page_w3; + sprinter_page_w3(phys_page); + memcpy((void *)(0xC000u + off), src, n); + sprinter_page_w3(saved); +} diff --git a/libc/mem/bank_write_w1.c b/libc/mem/bank_write_w1.c new file mode 100644 index 0000000..c06a4d9 --- /dev/null +++ b/libc/mem/bank_write_w1.c @@ -0,0 +1,18 @@ +/* + * bank_write_w1 — скопировать n байт из src в чужую физическую страницу + * (окно 1, порт 0xA2, база 0x4000) со свопом-возвратом W1. + * ТОЛЬКО для `--memory tiny` — см. bank_load_byte_w1.c. + */ + +#include +#include +#include +#include + +void bank_write_w1(uint8_t phys_page, uint16_t off, const void *src, uint16_t n) +{ + uint8_t saved = _io_page_w1; + sprinter_page_w1(phys_page); + memcpy((void *)(0x4000u + off), src, n); + sprinter_page_w1(saved); +} diff --git a/libc/mem/mem_alloc_pages_bios.c b/libc/mem/mem_alloc_pages_bios.c new file mode 100644 index 0000000..71a4eab --- /dev/null +++ b/libc/mem/mem_alloc_pages_bios.c @@ -0,0 +1,34 @@ +/* + * mem_alloc_pages_bios — выделить n смежных 16-КБ физических страниц + * из EMM-пула через BIOS EMM_FN2 ($C2, rst 8): B=npages → A=blk_id, + * CF=err. + * + * in: n — 1..255 (сколько страниц). + * out: blk_id (1..255) при успехе; 0 при ошибке (errno установлен). + * + * blk_id непрозрачен — отдай его mem_get_page_bios() за номерами + * физических страниц и mem_free_block_bios() по окончании. Id + * начинаются с 1; 0 зарезервирован как сигнал «не выделилось». + */ + +#include +#include + +uint8_t mem_alloc_pages_bios(uint8_t n) __naked +{ + (void)n; + __asm + ;; SDCC: единственный uint8-аргумент в A; BIOS хочет n в B. + push ix + ld b, a + ld c, #0xC2 ; BIOS EMM_FN2 (ALLOC) + rst #0x08 + pop ix + jr c, _alloc_fail + ret ; CF=0 → A = blk_id (возврат uint8 в A) + _alloc_fail: + call __errno_set ; CF=1 → A = код ошибки + xor a, a ; вернуть 0 = сигнал ошибки + ret + __endasm; +} diff --git a/libc/mem/mem_alloc_pages_estex.c b/libc/mem/mem_alloc_pages_estex.c new file mode 100644 index 0000000..f7ebe3e --- /dev/null +++ b/libc/mem/mem_alloc_pages_estex.c @@ -0,0 +1,31 @@ +/* + * mem_alloc_pages_estex — выделить n смежных 16-КБ физических страниц + * из EMM-пула через ESTEX GETMEM ($3D): B=npages → A=blk_id, CF=err. + * + * in: n — 1..255; out: blk_id (1..255) или 0 при ошибке (errno). + * + * blk_id непрозрачен — mem_get_page_bios() даст номера страниц, + * mem_free_block_estex() освободит. Id с 1; 0 = «не выделилось». + */ + +#include +#include + +uint8_t mem_alloc_pages_estex(uint8_t n) __naked +{ + (void)n; + __asm + ;; SDCC: единственный uint8-аргумент в A; ESTEX хочет n в B. + push ix + ld b, a + ld c, #0x3D ; ESTEX GETMEM + rst #0x10 + pop ix + jr c, _alloc_fail + ret ; CF=0 → A = blk_id (возврат uint8 в A) + _alloc_fail: + call __errno_set ; CF=1 → A = код ошибки + xor a, a ; вернуть 0 = сигнал ошибки + ret + __endasm; +} diff --git a/libc/mem/mem_bios.c b/libc/mem/mem_bios.c deleted file mode 100644 index 3de89d8..0000000 --- a/libc/mem/mem_bios.c +++ /dev/null @@ -1,138 +0,0 @@ -/* - * mem_alloc_pages / mem_free_block / mem_get_page / mem_info — ESTEX EMM - * wrappers for explicit 16 KB-page allocation. - * - * BIOS $C0 EMM_FN0 (MEMINFO) → HL=total pages, BC=free pages - * BIOS $C2 EMM_FN2 (ALLOC) B=npages → A=block id, CF=err - * BIOS $C3 EMM_FN3 (FREE) A=block id → CF=err - * BIOS $C4 EMM_FN4 (PAGEID) A=blk, B=idx → A=physical page CF=err - * BIOS $C6 EMM_FN6 (IOTEST) A=blk, B=idx → A=physical page CF=err - * - * Pattern: every RST 10h / RST 8 is bracketed with push/pop IX because - * ESTEX/BIOS clobber it and the C caller uses it as a frame pointer. - */ - -#include -#include - -/* - * Allocate `n` contiguous 16-KB physical pages from the EMM pool. - * - * in: n — 1..255 (number of pages requested). - * out: blk_id (1..255) on success; 0 on failure with errno set. - * - * The returned block id is opaque — pass it to mem_get_page() to obtain - * each physical-page number, and to mem_free_block() when done. Block - * ids start at 1; id 0 is reserved as the "allocation failed" sentinel. - */ -uint8_t mem_alloc_pages_bios(uint8_t n) __naked -{ - (void)n; - __asm - ;; SDCC single-uint8 arg → A on entry; ESTEX GETMEM wants n in B. - push ix - ld b, a - ld c, #0xC2 ; BIOS EMM_FN2 - rst #0x08 - pop ix - jr c, _alloc_fail - ret ; CF=0 → A = blk_id, return as uint8 in A - _alloc_fail: - call __errno_set ; CF=1 → A = ESTEX errcode - xor a, a ; return 0 = failure sentinel - ret - __endasm; -} - -/* - * Release a block previously returned by mem_alloc_pages(). - * - * in: blk_id (1..255). - * out: void; on ESTEX error errno is set (e.g. EINVAL for unknown id). - * - * Idempotent guarantees are NOT provided — freeing the same block twice - * sets errno on the second call. Caller is responsible for tracking - * ownership. - */ -void mem_free_block_bios(uint8_t blk_id) __naked -{ - (void)blk_id; - __asm - ;; SDCC single-uint8 arg → A on entry. - push ix - ld c, #0xC3 ; ESTEX FREEMEM - rst #0x08 - pop ix - ret nc ; CF=0 → success - jp __errno_set ; CF=1 → A = ESTEX errcode; tail-call helper - __endasm; -} - -/* - * Translate a (block, page-index) pair into a physical 16-KB page number, - * suitable for OUT to PORT_PAGE_W1/W2/W3 or for bank_*() helpers. - * - * in: blk_id — id returned by mem_alloc_pages(). - * idx — 0..(n-1), where n was the count passed to alloc. - * out: physical page number (1..255) on success; - * 0 on failure with errno set (invalid block or idx out of range). - */ -uint8_t mem_get_page_bios(uint8_t blk_id, uint8_t idx) __naked -{ - (void)blk_id; (void)idx; - __asm - ;; 2-arg uint8/uint8: blk_id → A, idx → L. - push ix - ld b, l ; BIOS wants idx in B - ;; A still has blk_id - ld c, #0xC4 ; BIOS EMM_GETPAGE - rst #0x08 - pop ix - ret nc ; CF=0 → A = phys page (return value) - ;; CF=1 → A = errcode; set errno, return 0 as sentinel. - call __errno_set - xor a, a - ret - __endasm; -} - -/* - * Query the EMM allocator about its current state. - * - * *total ← number of 16-KB physical pages installed in the system - * *free_pages ← number currently available for allocation - * - * Both pointers must be non-NULL writeable uint16_t locations. - * No error path: ESTEX INFOMEM always succeeds. - */ -void mem_info_bios(uint16_t *total, uint16_t *free_pages) __naked -{ - (void)total; (void)free_pages; - __asm - ;; HL = total ptr, DE = free_pages ptr on entry. - ;; RST 10 clobbers both — stash on the stack across the call. - push ix - push hl ; later [SP+2] = total_ptr - push de ; TOS [SP+0] = free_pages_ptr - - ld c, #0xC0 ; BIOS INFOMEM → HL = total, BC = free - rst #0x08 - - pop de ; DE = free_pages_ptr - ld a, c - ld (de), a - inc de - ld a, b - ld (de), a ; *free_pages = BC - - pop de ; DE = total_ptr - ld a, l - ld (de), a - inc de - ld a, h - ld (de), a ; *total = HL - - pop ix - ret - __endasm; -} diff --git a/libc/mem/mem_estex.c b/libc/mem/mem_estex.c deleted file mode 100644 index b6e2bb2..0000000 --- a/libc/mem/mem_estex.c +++ /dev/null @@ -1,121 +0,0 @@ -/* - * mem_alloc_pages / mem_free_block / mem_get_page / mem_info — ESTEX EMM - * wrappers for explicit 16 KB-page allocation. - * - * ESTEX $3C INFOMEM → HL=total pages, BC=free pages - * ESTEX $3D GETMEM B=npages → A=block id, CF=err - * ESTEX $3E FREEMEM A=block id → CF=err - * - * Pattern: every RST 10h / RST 8 is bracketed with push/pop IX because - * ESTEX/BIOS clobber it and the C caller uses it as a frame pointer. - */ - -#include -#include - -/* - * Allocate `n` contiguous 16-KB physical pages from the EMM pool. - * - * in: n — 1..255 (number of pages requested). - * out: blk_id (1..255) on success; 0 on failure with errno set. - * - * The returned block id is opaque — pass it to mem_get_page() to obtain - * each physical-page number, and to mem_free_block() when done. Block - * ids start at 1; id 0 is reserved as the "allocation failed" sentinel. - */ -uint8_t mem_alloc_pages_estex(uint8_t n) __naked -{ - (void)n; - __asm - ;; SDCC single-uint8 arg → A on entry; ESTEX GETMEM wants n in B. - push ix - ld b, a - ld c, #0x3D ; ESTEX GETMEM - rst #0x10 - pop ix - jr c, _alloc_fail - ret ; CF=0 → A = blk_id, return as uint8 in A - _alloc_fail: - call __errno_set ; CF=1 → A = ESTEX errcode - xor a, a ; return 0 = failure sentinel - ret - __endasm; -} - - -/* - * Release a block previously returned by mem_alloc_pages(). - * - * in: blk_id (1..255). - * out: void; on ESTEX error errno is set (e.g. EINVAL for unknown id). - * - * Idempotent guarantees are NOT provided — freeing the same block twice - * sets errno on the second call. Caller is responsible for tracking - * ownership. - */ -void mem_free_block_estex(uint8_t blk_id) __naked -{ - (void)blk_id; - __asm - ;; SDCC single-uint8 arg → A on entry. - push ix - ld c, #0x3E ; ESTEX FREEMEM - rst #0x10 - pop ix - ret nc ; CF=0 → success - jp __errno_set ; CF=1 → A = ESTEX errcode; tail-call helper - __endasm; -} - - -/* - * Translate a (block, page-index) pair into a physical 16-KB page number, - * suitable for OUT to PORT_PAGE_W1/W2/W3 or for bank_*() helpers. - * - * in: blk_id — id returned by mem_alloc_pages(). - * idx — 0..(n-1), where n was the count passed to alloc. - * out: physical page number (1..255) on success; - * 0 on failure with errno set (invalid block or idx out of range). - */ - -/* - * Query the EMM allocator about its current state. - * - * *total ← number of 16-KB physical pages installed in the system - * *free_pages ← number currently available for allocation - * - * Both pointers must be non-NULL writeable uint16_t locations. - * No error path: ESTEX INFOMEM always succeeds. - */ -void mem_info_estex(uint16_t *total, uint16_t *free_pages) __naked -{ - (void)total; (void)free_pages; - __asm - ;; HL = total ptr, DE = free_pages ptr on entry. - ;; RST 10 clobbers both — stash on the stack across the call. - push ix - push hl ; later [SP+2] = total_ptr - push de ; TOS [SP+0] = free_pages_ptr - - ld c, #0x3C ; ESTEX INFOMEM → HL = total, BC = free - rst #0x10 - - pop de ; DE = free_pages_ptr - ld a, c - ld (de), a - inc de - ld a, b - ld (de), a ; *free_pages = BC - - pop de ; DE = total_ptr - ld a, l - ld (de), a - inc de - ld a, h - ld (de), a ; *total = HL - - pop ix - ret - __endasm; -} - diff --git a/libc/mem/mem_free_block_bios.c b/libc/mem/mem_free_block_bios.c new file mode 100644 index 0000000..475f63d --- /dev/null +++ b/libc/mem/mem_free_block_bios.c @@ -0,0 +1,24 @@ +/* + * mem_free_block_bios — освободить блок, выданный mem_alloc_pages_bios. + * BIOS EMM_FN3 ($C3, rst 8): A=blk_id → CF=err. + * + * Идемпотентности НЕТ: повторное освобождение того же блока ставит + * errno. Владение отслеживает вызывающий. + */ + +#include +#include + +void mem_free_block_bios(uint8_t blk_id) __naked +{ + (void)blk_id; + __asm + ;; SDCC: единственный uint8-аргумент уже в A. + push ix + ld c, #0xC3 ; BIOS EMM_FN3 (FREE) + rst #0x08 + pop ix + ret nc ; CF=0 → успех + jp __errno_set ; CF=1 → A = код; tail-call хелпера + __endasm; +} diff --git a/libc/mem/mem_free_block_estex.c b/libc/mem/mem_free_block_estex.c new file mode 100644 index 0000000..d62b166 --- /dev/null +++ b/libc/mem/mem_free_block_estex.c @@ -0,0 +1,22 @@ +/* + * mem_free_block_estex — освободить блок, выданный + * mem_alloc_pages_estex. ESTEX FREEMEM ($3E): A=blk_id → CF=err. + * Повторное освобождение ставит errno; владение — на вызывающем. + */ + +#include +#include + +void mem_free_block_estex(uint8_t blk_id) __naked +{ + (void)blk_id; + __asm + ;; SDCC: единственный uint8-аргумент уже в A. + push ix + ld c, #0x3E ; ESTEX FREEMEM + rst #0x10 + pop ix + ret nc ; CF=0 → успех + jp __errno_set ; CF=1 → A = код; tail-call хелпера + __endasm; +} diff --git a/libc/mem/mem_get_page_bios.c b/libc/mem/mem_get_page_bios.c new file mode 100644 index 0000000..e40953c --- /dev/null +++ b/libc/mem/mem_get_page_bios.c @@ -0,0 +1,31 @@ +/* + * mem_get_page_bios — перевести пару (блок, индекс страницы) в номер + * физической 16-КБ страницы (годен для OUT в PORT_PAGE_W1/W2/W3 или + * bank_*-хелперов). BIOS EMM_FN4 ($C4, rst 8): A=blk, B=idx → A=page, + * CF=err. + * + * in: blk_id — id от mem_alloc_pages_*; idx — 0..(n-1). + * out: номер физической страницы (1..255); 0 при ошибке (errno). + */ + +#include +#include + +uint8_t mem_get_page_bios(uint8_t blk_id, uint8_t idx) __naked +{ + (void)blk_id; (void)idx; + __asm + ;; 2 аргумента uint8/uint8: blk_id → A, idx → L. + push ix + ld b, l ; BIOS хочет idx в B + ;; A всё ещё держит blk_id + ld c, #0xC4 ; BIOS EMM_GETPAGE + rst #0x08 + pop ix + ret nc ; CF=0 → A = физ. страница (возврат) + ;; CF=1 → A = код; ставим errno, возвращаем 0 как сигнал. + call __errno_set + xor a, a + ret + __endasm; +} diff --git a/libc/mem/mem_info_bios.c b/libc/mem/mem_info_bios.c new file mode 100644 index 0000000..3cdbf80 --- /dev/null +++ b/libc/mem/mem_info_bios.c @@ -0,0 +1,45 @@ +/* + * mem_info_bios — состояние EMM-аллокатора через BIOS INFOMEM ($C0, + * rst 8): HL=всего страниц, BC=свободно. + * + * *total ← всего 16-КБ физических страниц в системе + * *free_pages ← сколько сейчас доступно + * + * Оба указателя должны быть валидными uint16_t. Ошибок нет — INFOMEM + * всегда успешен. + */ + +#include +#include + +void mem_info_bios(uint16_t *total, uint16_t *free_pages) __naked +{ + (void)total; (void)free_pages; + __asm + ;; HL = total ptr, DE = free_pages ptr на входе. + ;; RST портит оба — прячем на стек через вызов. + push ix + push hl ; потом [SP+2] = total_ptr + push de ; TOS [SP+0] = free_pages_ptr + + ld c, #0xC0 ; BIOS INFOMEM → HL = total, BC = free + rst #0x08 + + pop de ; DE = free_pages_ptr + ld a, c + ld (de), a + inc de + ld a, b + ld (de), a ; *free_pages = BC + + pop de ; DE = total_ptr + ld a, l + ld (de), a + inc de + ld a, h + ld (de), a ; *total = HL + + pop ix + ret + __endasm; +} diff --git a/libc/mem/mem_info_estex.c b/libc/mem/mem_info_estex.c new file mode 100644 index 0000000..0ec225a --- /dev/null +++ b/libc/mem/mem_info_estex.c @@ -0,0 +1,44 @@ +/* + * mem_info_estex — состояние EMM-аллокатора через ESTEX INFOMEM ($3C): + * HL=всего страниц, BC=свободно. + * + * *total ← всего 16-КБ физических страниц в системе + * *free_pages ← сколько сейчас доступно + * + * Оба указателя должны быть валидными uint16_t. Ошибок нет. + */ + +#include +#include + +void mem_info_estex(uint16_t *total, uint16_t *free_pages) __naked +{ + (void)total; (void)free_pages; + __asm + ;; HL = total ptr, DE = free_pages ptr на входе. + ;; RST 10 портит оба — прячем на стек через вызов. + push ix + push hl ; потом [SP+2] = total_ptr + push de ; TOS [SP+0] = free_pages_ptr + + ld c, #0x3C ; ESTEX INFOMEM → HL = total, BC = free + rst #0x10 + + pop de ; DE = free_pages_ptr + ld a, c + ld (de), a + inc de + ld a, b + ld (de), a ; *free_pages = BC + + pop de ; DE = total_ptr + ld a, l + ld (de), a + inc de + ld a, h + ld (de), a ; *total = HL + + pop ix + ret + __endasm; +} diff --git a/libc/mouse/_mouse.h b/libc/mouse/_mouse.h new file mode 100644 index 0000000..b13699c --- /dev/null +++ b/libc/mouse/_mouse.h @@ -0,0 +1,21 @@ +/* + * _mouse.h — внутренние скретчи и хелперы мышиного драйвера (RST 30h). + * НЕ публичный заголовок; данные в _mouse_scratch.c, хелпер в _ms_query.c. + */ +#ifndef _MOUSE_INTERNAL_H +#define _MOUSE_INTERNAL_H + +#include + +/* Скретч курсора для IX-конвенции LOAD/RETURN CURSOR ($09/$0B): + * RST 30h берёт указатель на битмап в IX, поля передаём через память. */ +extern uint16_t _mc_image; +extern uint8_t _mc_width; +extern uint8_t _mc_height; +extern uint8_t _mc_hot_x; +extern uint8_t _mc_hot_y; + +/* GET SENSITIVITY ($0E): возвращает (vert<<8)|horz. */ +int _ms_query(void); + +#endif diff --git a/libc/mouse/_mouse_scratch.c b/libc/mouse/_mouse_scratch.c new file mode 100644 index 0000000..ba93c44 --- /dev/null +++ b/libc/mouse/_mouse_scratch.c @@ -0,0 +1,13 @@ +/* + * _mouse_scratch — скретч-поля курсора, общие для mouse_load_cursor и + * mouse_get_cursor (обмен с asm по известным именам, т.к. RST 30h + * использует IX и портит регистры). Модуль только с данными. + */ + +#include "_mouse.h" + +uint16_t _mc_image; +uint8_t _mc_width; +uint8_t _mc_height; +uint8_t _mc_hot_x; +uint8_t _mc_hot_y; diff --git a/libc/mouse/_ms_query.c b/libc/mouse/_ms_query.c new file mode 100644 index 0000000..f87752a --- /dev/null +++ b/libc/mouse/_ms_query.c @@ -0,0 +1,20 @@ +/* + * _ms_query — GET SENSITIVITY (RST 30h, fn $0E): H=верт, L=гориз; + * возвращаем (vert<<8)|horz в DE. Общий хелпер для + * mouse_get_sensitivity_x/y. + */ + +#include "_mouse.h" + +int _ms_query(void) __naked +{ + __asm + push ix + ld c, #0x0E ; GET SENSITIVITY + rst #0x30 ; MOUSE + ld d, h + ld e, l + pop ix + ret + __endasm; +} diff --git a/libc/mouse/mouse.c b/libc/mouse/mouse.c deleted file mode 100644 index 287c366..0000000 --- a/libc/mouse/mouse.c +++ /dev/null @@ -1,305 +0,0 @@ -/* - * mouse.c — Sprinter mouse driver wrappers (RST 30h). - * - * All calls use the same pattern as ESTEX (push/pop IX around the RST) - * since the driver doesn't promise to preserve registers either. - */ - -#include - -/* Scratch for READ_STATE — RST 30h clobbers HL/DE/A so we can't keep the - * state pointer in HL across the call. Explicit `= 0` so SDCC reserves - * real BSS storage — see memory/sdcc_static_storage_gotcha.md. */ -static uint16_t mb_x = 0, mb_y = 0; -static uint8_t mb_buttons = 0; - -int mouse_init(void) __naked -{ - __asm - push ix - ld c, #0x00 ; INITIALIZATION - rst #0x30 ; MOUSE - pop ix - jr c, _mi_err - ld de, #0 - ret - _mi_err: - ld de, #-1 - ret - __endasm; -} - -void mouse_show(void) __naked -{ - __asm - push ix - ld c, #0x01 ; SHOW MOUSE CURSOR - rst #0x30 ; MOUSE - pop ix - ret - __endasm; -} - -void mouse_hide(void) __naked -{ - __asm - push ix - ld c, #0x02 ; HIDE MOUSE CURSOR - rst #0x30 ; MOUSE - pop ix - ret - __endasm; -} - -void mouse_refresh(void) __naked -{ - __asm - push ix - ld c, #0x83 ; MOUSE REFRESH - rst #0x30 ; MOUSE - pop ix - ret - __endasm; -} - -void mouse_read(mouse_state_t *st) __naked -{ - (void)st; - __asm - ;; HL = state ptr on entry. - push ix - push hl ; stash ptr across RST - ld c, #0x03 ; READ MOUSE STATE - rst #0x30 ; MOUSE - ;; Returns: A=buttons, HL=x, DE=y (CF=err but we ignore here) - ld (_mb_x), hl - ld (_mb_y), de - ld (_mb_buttons), a - pop hl ; restore state ptr - pop ix - - ;; Copy scratch → *st. Struct layout: x(2), y(2), buttons(1). - ld de, (_mb_x) - ld (hl), e - inc hl - ld (hl), d - inc hl - ld de, (_mb_y) - ld (hl), e - inc hl - ld (hl), d - inc hl - ld a, (_mb_buttons) - ld (hl), a - ret - __endasm; -} - -void mouse_goto(int x, int y) __naked -{ - (void)x; (void)y; - __asm - ;; HL = x, DE = y. - push ix - ld c, #0x04 ; GOTO MOUSE CURSOR - rst #0x30 ; MOUSE - pop ix - ret - __endasm; -} - -void mouse_bounds_x(int xmin, int xmax) __naked -{ - (void)xmin; (void)xmax; - __asm - ;; HL = xmin, DE = xmax. - push ix - ld c, #0x08 ; HORZ BOUNDS - rst #0x30 ; MOUSE - pop ix - ret - __endasm; -} - -void mouse_bounds_y(int ymin, int ymax) __naked -{ - (void)ymin; (void)ymax; - __asm - ;; HL = ymin, DE = ymax. - push ix - ld c, #0x07 ; VERT BOUNDS - rst #0x30 ; MOUSE - pop ix - ret - __endasm; -} - -/* ---- $09 LOAD CURSOR + $0B RETURN CURSOR ---------------------- */ -/* Scratch for the IX-passing convention — RST 30h takes the bitmap - * pointer in IX, so we have to set it up explicitly. - * - * Initialised to 0 so SDCC reserves real BSS storage — uninitialised - * `static uint8_t` declarations can coalesce to a single address and - * stomp on each other. See memory/sdcc_static_storage_gotcha.md. */ -static uint16_t mc_image = 0; -static uint8_t mc_width = 0; -static uint8_t mc_height = 0; -static uint8_t mc_hot_x = 0; -static uint8_t mc_hot_y = 0; -/* Saved struct pointer for mouse_get_cursor. SDCC __sdcccall(1) passes - * `c` in HL, then stashes it in DE around the inline asm. Our asm - * clobbers DE (driver returns hot_y/hot_x in D/E) so the post-asm - * `c->width = ...` writes would otherwise land at a garbage address. - * We park the pointer in BSS instead so SDCC re-fetches it from - * memory after the asm. */ -static mouse_cursor_t *mc_dest = 0; - -void mouse_load_cursor(const mouse_cursor_t *c) -{ - /* Copy fields out of the C struct into our scratch globals so the - * asm side has well-known names. */ - mc_image = (uint16_t)(uintptr_t)c->image; - mc_width = c->width; - mc_height = c->height; - mc_hot_x = c->hot_x; - mc_hot_y = c->hot_y; - __asm - push ix - ld ix, (_mc_image) - ld a, (_mc_height) - ld h, a - ld a, (_mc_width) - ld l, a - ld a, (_mc_hot_y) - ld d, a - ld a, (_mc_hot_x) - ld e, a - ld b, #0 - ld c, #0x09 ; LOAD MOUSE CURSOR - rst #0x30 ; MOUSE - pop ix - __endasm; -} - -void mouse_get_cursor(mouse_cursor_t *c) -{ - mc_dest = c; /* park ptr in BSS */ - mc_image = (uint16_t)(uintptr_t)c->image; - __asm - push ix - ld ix, (_mc_image) ; IX = bitmap buffer from caller - ld c, #0x0B ; RETURN CURSOR - rst #0x30 ; mouse driver, NOT ESTEX - ;; Returns: H=height, L=width, D=hot_y, E=hot_x. - ld a, h - ld (_mc_height), a - ld a, l - ld (_mc_width), a - ld a, d - ld (_mc_hot_y), a - ld a, e - ld (_mc_hot_x), a - pop ix - __endasm; - /* Re-fetch the struct pointer from BSS — `c` (kept in DE by SDCC - * around the inline asm) was clobbered by the RST 30h above. */ - mouse_cursor_t *p = mc_dest; - p->width = mc_width; - p->height = mc_height; - p->hot_x = mc_hot_x; - p->hot_y = mc_hot_y; -} - -/* ---- $0E / $0F SENSITIVITY ------------------------------------ */ -/* GET returns H=vert, L=horz in HL. We expose the two halves as - * separate getters so the simple "uint8_t" return ABI works cleanly. */ - -static int ms_query(void) __naked -{ - __asm - push ix - ld c, #0x0E ; GET SENSITIVITY - rst #0x30 ; MOUSE - ld d, h - ld e, l - pop ix - ret - __endasm; -} - -uint8_t mouse_get_sensitivity_x(void) -{ - return (uint8_t)(ms_query() & 0xFF); /* E = horz */ -} - -uint8_t mouse_get_sensitivity_y(void) -{ - return (uint8_t)(ms_query() >> 8); /* D = vert */ -} - -void mouse_set_sensitivity(uint8_t horz, uint8_t vert) __naked -{ - (void)horz; (void)vert; - /* Pack into HL: H=vert, L=horz. */ - __asm - push ix - ld h, l - ld l, a - ld c, #0x0F ; SET SENSITIVITY - rst #0x30 ; MOUSE - pop ix - __endasm; -} - -/* ---- $81 CHANGE VIDEO MODE ------------------------------------ */ -/* SDCC __sdcccall(1): single uint8_t arg arrives in A. */ -void mouse_video_mode_changed(uint8_t mode) __naked -{ - (void)mode; - __asm - push ix - ;; A already holds the mode byte (from SDCC ABI). - ld c, #0x81 ; CHANGE VIDEO MODE - rst #0x30 ; MOUSE - pop ix - ret - __endasm; -} - -/* CURSOR_TEXT_MODES ($0A): - * B = 0 - * H = AND symbol mask L = XOR symbol mask - * D = AND attribute mask E = XOR attribute mask - * - * SDCC __sdcccall(1) gives us: - * sym_and in L (low byte of HL arg) - * sym_xor in E (low byte of DE arg) - * attr_and at [SP+2] - * attr_xor at [SP+3] - */ -void mouse_text_cursor(uint8_t sym_and, uint8_t sym_xor, - uint8_t attr_and, uint8_t attr_xor) __naked -{ - (void)sym_and; (void)sym_xor; (void)attr_and; (void)attr_xor; - __asm - ;; SDCC __sdcccall(1) for 4×uint8_t args: - ;; arg1 sym_and → A - ;; arg2 sym_xor → L - ;; arg3 attr_and → stack low byte (packed into HL.L on caller, push HL) - ;; arg4 attr_xor → stack high byte (HL.H pushed by caller) - pop iy ; return address - pop bc ; C = attr_and (low), B = attr_xor (high) - - push ix - ;; Target: H=sym_and, L=sym_xor, D=attr_and, E=attr_xor, B=0 - ld h, a ; H = sym_and (from A) - ; L already holds sym_xor - ld d, c ; D = attr_and - ld e, b ; E = attr_xor - ld b, #0 - ld c, #0x0A ; CURSOR TEXT MODE - rst #0x30 ; MOUSE - pop ix - jp (iy) - __endasm; -} diff --git a/libc/mouse/mouse_bounds_x.c b/libc/mouse/mouse_bounds_x.c new file mode 100644 index 0000000..62893d9 --- /dev/null +++ b/libc/mouse/mouse_bounds_x.c @@ -0,0 +1,19 @@ +/* + * mouse_bounds_x — ограничить движение курсора по горизонтали + * [xmin..xmax] (RST 30h, fn $08). + */ + +#include + +void mouse_bounds_x(int xmin, int xmax) __naked +{ + (void)xmin; (void)xmax; + __asm + ;; HL = xmin, DE = xmax. + push ix + ld c, #0x08 ; HORZ BOUNDS + rst #0x30 ; MOUSE + pop ix + ret + __endasm; +} diff --git a/libc/mouse/mouse_bounds_y.c b/libc/mouse/mouse_bounds_y.c new file mode 100644 index 0000000..797033c --- /dev/null +++ b/libc/mouse/mouse_bounds_y.c @@ -0,0 +1,19 @@ +/* + * mouse_bounds_y — ограничить движение курсора по вертикали + * [ymin..ymax] (RST 30h, fn $07). + */ + +#include + +void mouse_bounds_y(int ymin, int ymax) __naked +{ + (void)ymin; (void)ymax; + __asm + ;; HL = ymin, DE = ymax. + push ix + ld c, #0x07 ; VERT BOUNDS + rst #0x30 ; MOUSE + pop ix + ret + __endasm; +} diff --git a/libc/mouse/mouse_get_cursor.c b/libc/mouse/mouse_get_cursor.c new file mode 100644 index 0000000..b42be68 --- /dev/null +++ b/libc/mouse/mouse_get_cursor.c @@ -0,0 +1,42 @@ +/* + * mouse_get_cursor — прочитать текущую форму курсора в *c (RST 30h, + * fn $0B RETURN CURSOR). Битмап пишется по c->image (указатель уходит + * в IX), поля возвращаются в H/L/D/E и идут в структуру через общий + * скретч (_mouse.h). + */ + +#include "_mouse.h" +#include + +/* Припаркованный указатель структуры: SDCC держит `c` в DE вокруг + * inline asm, а RST 30h портит DE (драйвер возвращает hot_y/hot_x в + * D/E) — без парковки записи полей ушли бы по мусорному адресу. */ +static mouse_cursor_t *mc_dest; + +void mouse_get_cursor(mouse_cursor_t *c) +{ + mc_dest = c; /* припарковать в BSS */ + _mc_image = (uint16_t)(uintptr_t)c->image; + __asm + push ix + ld ix, (__mc_image) ; IX = буфер битмапа вызывающего + ld c, #0x0B ; RETURN CURSOR + rst #0x30 ; мышиный драйвер, НЕ ESTEX + ;; Возврат: H=height, L=width, D=hot_y, E=hot_x. + ld a, h + ld (__mc_height), a + ld a, l + ld (__mc_width), a + ld a, d + ld (__mc_hot_y), a + ld a, e + ld (__mc_hot_x), a + pop ix + __endasm; + /* Перечитать указатель из BSS — DE с `c` испорчен RST'ом выше. */ + mouse_cursor_t *p = mc_dest; + p->width = _mc_width; + p->height = _mc_height; + p->hot_x = _mc_hot_x; + p->hot_y = _mc_hot_y; +} diff --git a/libc/mouse/mouse_get_sensitivity_x.c b/libc/mouse/mouse_get_sensitivity_x.c new file mode 100644 index 0000000..3ad2e68 --- /dev/null +++ b/libc/mouse/mouse_get_sensitivity_x.c @@ -0,0 +1,11 @@ +/* + * mouse_get_sensitivity_x — горизонтальная чувствительность (делитель: + * меньше = быстрее; документация утверждает обратное — неверно). + */ + +#include "_mouse.h" + +uint8_t mouse_get_sensitivity_x(void) +{ + return (uint8_t)(_ms_query() & 0xFF); /* E = horz */ +} diff --git a/libc/mouse/mouse_get_sensitivity_y.c b/libc/mouse/mouse_get_sensitivity_y.c new file mode 100644 index 0000000..c679997 --- /dev/null +++ b/libc/mouse/mouse_get_sensitivity_y.c @@ -0,0 +1,11 @@ +/* + * mouse_get_sensitivity_y — вертикальная чувствительность (делитель: + * меньше = быстрее). + */ + +#include "_mouse.h" + +uint8_t mouse_get_sensitivity_y(void) +{ + return (uint8_t)(_ms_query() >> 8); /* D = vert */ +} diff --git a/libc/mouse/mouse_goto.c b/libc/mouse/mouse_goto.c new file mode 100644 index 0000000..ab4cc52 --- /dev/null +++ b/libc/mouse/mouse_goto.c @@ -0,0 +1,18 @@ +/* + * mouse_goto — переставить курсор мыши в (x, y) (RST 30h, fn $04). + */ + +#include + +void mouse_goto(int x, int y) __naked +{ + (void)x; (void)y; + __asm + ;; HL = x, DE = y. + push ix + ld c, #0x04 ; GOTO MOUSE CURSOR + rst #0x30 ; MOUSE + pop ix + ret + __endasm; +} diff --git a/libc/mouse/mouse_hide.c b/libc/mouse/mouse_hide.c new file mode 100644 index 0000000..92e30c7 --- /dev/null +++ b/libc/mouse/mouse_hide.c @@ -0,0 +1,16 @@ +/* + * mouse_hide — спрятать курсор мыши (RST 30h, fn $02). + */ + +#include + +void mouse_hide(void) __naked +{ + __asm + push ix + ld c, #0x02 ; HIDE MOUSE CURSOR + rst #0x30 ; MOUSE + pop ix + ret + __endasm; +} diff --git a/libc/mouse/mouse_init.c b/libc/mouse/mouse_init.c new file mode 100644 index 0000000..2903552 --- /dev/null +++ b/libc/mouse/mouse_init.c @@ -0,0 +1,23 @@ +/* + * mouse_init — инициализация мышиного драйвера (RST 30h, fn $00). + * Возвращает 0 или -1, если драйвер недоступен (CF=1). + * Все вызовы RST 30h — с push/pop IX: драйвер регистры не бережёт. + */ + +#include + +int mouse_init(void) __naked +{ + __asm + push ix + ld c, #0x00 ; INITIALIZATION + rst #0x30 ; MOUSE + pop ix + jr c, _mi_err + ld de, #0 + ret + _mi_err: + ld de, #-1 + ret + __endasm; +} diff --git a/libc/mouse/mouse_load_cursor.c b/libc/mouse/mouse_load_cursor.c new file mode 100644 index 0000000..c766677 --- /dev/null +++ b/libc/mouse/mouse_load_cursor.c @@ -0,0 +1,35 @@ +/* + * mouse_load_cursor — загрузить форму курсора из *c (RST 30h, fn $09 + * LOAD MOUSE CURSOR). Драйвер берёт указатель битмапа в IX, поля — + * в H/L/D/E; переносим их из структуры через общий скретч (_mouse.h), + * чтобы у asm были известные имена. + */ + +#include "_mouse.h" +#include + +void mouse_load_cursor(const mouse_cursor_t *c) +{ + /* Разложить поля структуры по скретч-переменным для asm. */ + _mc_image = (uint16_t)(uintptr_t)c->image; + _mc_width = c->width; + _mc_height = c->height; + _mc_hot_x = c->hot_x; + _mc_hot_y = c->hot_y; + __asm + push ix + ld ix, (__mc_image) + ld a, (__mc_height) + ld h, a + ld a, (__mc_width) + ld l, a + ld a, (__mc_hot_y) + ld d, a + ld a, (__mc_hot_x) + ld e, a + ld b, #0 + ld c, #0x09 ; LOAD MOUSE CURSOR + rst #0x30 ; MOUSE + pop ix + __endasm; +} diff --git a/libc/mouse/mouse_read.c b/libc/mouse/mouse_read.c new file mode 100644 index 0000000..4152149 --- /dev/null +++ b/libc/mouse/mouse_read.c @@ -0,0 +1,46 @@ +/* + * mouse_read — снять состояние мыши в *st (RST 30h, fn $03). + * Возвращает: A=кнопки, HL=x, DE=y — RST портит HL/DE/A, поэтому + * указатель на структуру не удержать в регистрах: результат идёт через + * приватный скретч в BSS, потом копируется в *st. + * Раскладка mouse_state_t: x(2), y(2), buttons(1). + */ + +#include + +/* Скретч только для mouse_read. */ +static uint16_t mb_x, mb_y; +static uint8_t mb_buttons; + +void mouse_read(mouse_state_t *st) __naked +{ + (void)st; + __asm + ;; HL = указатель на структуру на входе. + push ix + push hl ; спрятать указатель через RST + ld c, #0x03 ; READ MOUSE STATE + rst #0x30 ; MOUSE + ;; Возврат: A=кнопки, HL=x, DE=y (CF=err — здесь игнорируем) + ld (_mb_x), hl + ld (_mb_y), de + ld (_mb_buttons), a + pop hl ; вернуть указатель + pop ix + + ;; Скопировать скретч → *st. Раскладка: x(2), y(2), buttons(1). + ld de, (_mb_x) + ld (hl), e + inc hl + ld (hl), d + inc hl + ld de, (_mb_y) + ld (hl), e + inc hl + ld (hl), d + inc hl + ld a, (_mb_buttons) + ld (hl), a + ret + __endasm; +} diff --git a/libc/mouse/mouse_refresh.c b/libc/mouse/mouse_refresh.c new file mode 100644 index 0000000..753ebc3 --- /dev/null +++ b/libc/mouse/mouse_refresh.c @@ -0,0 +1,17 @@ +/* + * mouse_refresh — перерисовать курсор мыши (RST 30h, fn $83) — после + * того как приложение изменило экран под ним. + */ + +#include + +void mouse_refresh(void) __naked +{ + __asm + push ix + ld c, #0x83 ; MOUSE REFRESH + rst #0x30 ; MOUSE + pop ix + ret + __endasm; +} diff --git a/libc/mouse/mouse_set_sensitivity.c b/libc/mouse/mouse_set_sensitivity.c new file mode 100644 index 0000000..34ef047 --- /dev/null +++ b/libc/mouse/mouse_set_sensitivity.c @@ -0,0 +1,20 @@ +/* + * mouse_set_sensitivity — установить чувствительность (RST 30h, fn $0F): + * H=верт, L=гориз. Значения — делители: меньше = быстрее. + */ + +#include + +void mouse_set_sensitivity(uint8_t horz, uint8_t vert) __naked +{ + (void)horz; (void)vert; + /* Упаковать в HL: H=vert, L=horz. */ + __asm + push ix + ld h, l + ld l, a + ld c, #0x0F ; SET SENSITIVITY + rst #0x30 ; MOUSE + pop ix + __endasm; +} diff --git a/libc/mouse/mouse_show.c b/libc/mouse/mouse_show.c new file mode 100644 index 0000000..0bdc041 --- /dev/null +++ b/libc/mouse/mouse_show.c @@ -0,0 +1,16 @@ +/* + * mouse_show — показать курсор мыши (RST 30h, fn $01). + */ + +#include + +void mouse_show(void) __naked +{ + __asm + push ix + ld c, #0x01 ; SHOW MOUSE CURSOR + rst #0x30 ; MOUSE + pop ix + ret + __endasm; +} diff --git a/libc/mouse/mouse_text_cursor.c b/libc/mouse/mouse_text_cursor.c new file mode 100644 index 0000000..c529e99 --- /dev/null +++ b/libc/mouse/mouse_text_cursor.c @@ -0,0 +1,39 @@ +/* + * mouse_text_cursor — форма курсора в текстовых режимах (RST 30h, + * fn $0A CURSOR_TEXT_MODES): + * B = 0 + * H = AND-маска символа L = XOR-маска символа + * D = AND-маска атрибута E = XOR-маска атрибута + * + * SDCC __sdcccall(1) для 4×uint8_t даёт: + * sym_and в A, sym_xor в L, attr_and/attr_xor парой на стеке. + */ + +#include + +void mouse_text_cursor(uint8_t sym_and, uint8_t sym_xor, + uint8_t attr_and, uint8_t attr_xor) __naked +{ + (void)sym_and; (void)sym_xor; (void)attr_and; (void)attr_xor; + __asm + ;; SDCC __sdcccall(1) для 4×uint8_t: + ;; arg1 sym_and → A + ;; arg2 sym_xor → L + ;; arg3 attr_and → младший байт пары на стеке + ;; arg4 attr_xor → старший байт пары + pop iy ; адрес возврата + pop bc ; C = attr_and (low), B = attr_xor (high) + + push ix + ;; Цель: H=sym_and, L=sym_xor, D=attr_and, E=attr_xor, B=0 + ld h, a ; H = sym_and (из A) + ; L уже держит sym_xor + ld d, c ; D = attr_and + ld e, b ; E = attr_xor + ld b, #0 + ld c, #0x0A ; CURSOR TEXT MODE + rst #0x30 ; MOUSE + pop ix + jp (iy) + __endasm; +} diff --git a/libc/mouse/mouse_video_mode_changed.c b/libc/mouse/mouse_video_mode_changed.c new file mode 100644 index 0000000..26db528 --- /dev/null +++ b/libc/mouse/mouse_video_mode_changed.c @@ -0,0 +1,20 @@ +/* + * mouse_video_mode_changed — сообщить драйверу мыши о смене видеорежима + * (RST 30h, fn $81), чтобы он пересчитал отрисовку курсора. Звать + * после settextmode()/gfx_init(). + */ + +#include + +void mouse_video_mode_changed(uint8_t mode) __naked +{ + (void)mode; + __asm + push ix + ;; A уже держит байт режима (SDCC __sdcccall(1)). + ld c, #0x81 ; CHANGE VIDEO MODE + rst #0x30 ; MOUSE + pop ix + ret + __endasm; +} diff --git a/libc/stdio/dec_print.c b/libc/stdio/dec_print.c index 380cf85..7b8ef5f 100644 --- a/libc/stdio/dec_print.c +++ b/libc/stdio/dec_print.c @@ -1,31 +1,33 @@ /* - * dec_print.c — compact decimal print primitives ported from solid-c - * (third_party/solid-c/SRC/CLIB/STDLIB.ASM, modules dec8/dec16/dec32). + * dec_print.c — компактная десятичная печать, портировано из solid-c + * (third_party/solid-c/SRC/CLIB/STDLIB.ASM, модули dec8/dec16/dec32). * - * void dec8 (uint8_t v) — no-leading-zero "0" .. "255" - * void dec16(uint16_t v) — no-leading-zero "0" .. "65535" - * void dec32(uint32_t v) — no-leading-zero "0" .. "4294967295" + * void dec8 (uint8_t v) — без ведущих нулей, "0" .. "255" + * void dec16(uint16_t v) — без ведущих нулей, "0" .. "65535" + * void dec32(uint32_t v) — без ведущих нулей, "0" .. "4294967295" * - * Algorithm: subtract-power-of-10 with running counter, then emit the - * digit unless we are still on leading zeros. 32-bit values use a - * BC/DE/HL pair plus the shadow set (exx) for the high half. + * Алгоритм: вычитание степени десяти со счётчиком, цифра печатается, + * если ведущие нули уже кончились. 32-битные значения живут в паре + * BC/DE/HL плюс теневой набор (exx) для старшей половины. * - * Layout: dec32 is the master routine; dec8/dec16 are tiny wrappers - * that prepare state and jump to internal entry points (__dec_entry3 - * for "last 3 digits", __dec_entry5 for "last 5") — same idea as - * solid-c, sharing most of the per-digit code. + * Раскладка: dec32 — главная рутина; dec8/dec16 — крошечные обёртки, + * готовящие состояние и прыгающие на внутренние входы (__dec_entry3 — + * «последние 3 цифры», __dec_entry5 — «последние 5») — как в solid-c, + * per-digit код общий. Поэтому файл сознательно НЕ разрезан на три + * модуля: dec8 всё равно притянул бы тело dec32 + * (см. docs/libc-split-asm-cases.md, случай 1). * - * ESTEX PUTCHAR ($5B) preserves IX (empirically verified) so no - * push/pop ix around the RST. + * ESTEX PUTCHAR ($5B) сохраняет IX (проверено эмпирически), так что + * push/pop ix вокруг RST не нужен. */ #include #include -/* Leading-zero suppression flag — 0 means "still skipping zeros", - * non-zero means "first non-zero digit seen, print everything from - * here on (including subsequent zeros)". */ -static uint8_t dec_flag = 0; +/* Флаг подавления ведущих нулей: 0 — «всё ещё пропускаем нули», + * не-0 — «первая значащая цифра встречена, дальше печатаем всё + * (включая последующие нули)». */ +static uint8_t dec_flag; void dec8(uint8_t v) __naked { @@ -33,9 +35,9 @@ void dec8(uint8_t v) __naked __asm ;; A = v. ld l, a - ld h, #0 ; HL = value + ld h, #0 ; HL = значение xor a, a - ld (_dec_flag), a ; reset suppress-leading-zero flag + ld (_dec_flag), a ; сбросить флаг подавления нулей jp __dec_entry3 __endasm; } @@ -46,7 +48,7 @@ void dec16(uint16_t v) __naked __asm ;; HL = v. exx - ld hl, #0 ; HL alt = 0 (high 16 of composite) + ld hl, #0 ; HL-альт = 0 (старшие 16 составного) exx xor a, a ld (_dec_flag), a @@ -58,17 +60,17 @@ void dec32(uint32_t v) __naked { (void)v; __asm - ;; HL = high16, DE = low16 on entry (SDCC HLDE). - ;; Move high16 into HL alt (shadow set), low16 into HL. + ;; На входе HL = high16, DE = low16 (SDCC HLDE). + ;; Старшие 16 — в HL-альт (теневой набор), младшие — в HL. push hl exx - pop hl ; HL alt = high16 + pop hl ; HL-альт = high16 exx ex de, hl ; HL = low16 xor a, a ld (_dec_flag), a - ;; ---- 5 most-significant decades (1e9..1e5) ---- + ;; ---- 5 старших декад (1e9..1e5) ---- ld de, #0xCA00 exx ld de, #0x3B9A ; 0x3B9ACA00 = 1,000,000,000 @@ -99,7 +101,7 @@ void dec32(uint32_t v) __naked exx call _dec_get_d32 - __dec_entry5:: ; entered from dec16 + __dec_entry5:: ; вход из dec16 ld de, #10000 exx ld de, #0 @@ -109,38 +111,38 @@ void dec32(uint32_t v) __naked ld de, #1000 call _dec_get_d16 - __dec_entry3:: ; entered from dec8 + __dec_entry3:: ; вход из dec8 ld de, #100 call _dec_get_d16 ld de, #10 call _dec_get_d16 - ;; Units digit — always emitted (so dec*(0) prints "0"). + ;; Цифра единиц — печатается всегда (dec*(0) выводит "0"). ld a, l add a, #0x30 ld c, #0x5B rst #0x10 ret - ;; ---- 32-bit: how many times DE+DE_alt fits in HL+HL_alt ---- + ;; ---- 32 бита: сколько раз DE+DE-альт укладывается в HL+HL-альт ---- _dec_get_d32: - ld a, #0x2F ; 0x2F = '0' minus 1 (pre-decrement) + ld a, #0x2F ; 0x2F = символ перед 0 (пред-декремент) and a, a ; CF = 0 _dec_get_d32_loop: inc a - sbc hl, de ; low half + sbc hl, de ; младшая половина exx - sbc hl, de ; high half (with chained borrow) + sbc hl, de ; старшая половина (с цепочкой заёма) exx jp nc, _dec_get_d32_loop - ;; Overshot — restore the last good value. + ;; Перебрали — вернуть последнее корректное значение. add hl, de exx adc hl, de exx jr _dec_emit_or_skip - ;; ---- 16-bit: how many times DE fits in HL ---- + ;; ---- 16 бит: сколько раз DE укладывается в HL ---- _dec_get_d16: ld a, #0x2F and a, a @@ -149,28 +151,27 @@ void dec32(uint32_t v) __naked sbc hl, de jp nc, _dec_get_d16_loop add hl, de - ;; Fall through to emit/skip. + ;; Fall-through в emit/skip. _dec_emit_or_skip: - ;; A = digit char in 0x30..0x39. If non-'0', latch the flag. - ;; Print only if flag is non-zero. - ld b, a ; save digit across the flag test + ;; A = символ цифры 0x30..0x39. Не-ноль защёлкивает флаг. + ;; Печатаем только при взведённом флаге. + ld b, a ; спасти цифру на время проверки флага cp a, #0x30 jr z, _dec_check_flag - ld (_dec_flag), a ; non-zero digit seen + ld (_dec_flag), a ; встречена значащая цифра _dec_check_flag: ld a, (_dec_flag) or a, a - ld a, b ; restore digit (ld does not touch flags) - ret z ; leading zero — skip print + ld a, b ; вернуть цифру (ld флаги не трогает) + ret z ; ведущий ноль — печать пропустить - ;; ESTEX PUTCHAR ($5B) preserves the main register set (BC, DE, - ;; HL, IX) but CLOBBERS the shadow set (BC alt, DE alt, HL alt). - ;; The 32-bit subtract-power-of-10 loop in _dec_get_d32 keeps - ;; the high 16 bits of the running remainder in HL alt, so we - ;; save/restore HL alt around the RST. Main HL (= low 16 of - ;; remainder) survives the call untouched, no save needed. - ;; See memory/estex_putchar_abi.md. + ;; ESTEX PUTCHAR ($5B) сохраняет основной набор (BC, DE, HL, + ;; IX), но КЛОББЕРИТ теневой (BC/DE/HL-альт). Цикл вычитания + ;; степени десяти в _dec_get_d32 держит старшие 16 бит остатка + ;; в HL-альт, поэтому HL-альт сохраняем вокруг RST. Основной + ;; HL (младшие 16 остатка) переживает вызов сам, спасать не + ;; нужно. См. memory/estex_putchar_abi.md. exx push hl exx diff --git a/libc/stdio/getchar.c b/libc/stdio/getchar.c index 7c1fcaa..8cb9296 100644 --- a/libc/stdio/getchar.c +++ b/libc/stdio/getchar.c @@ -1,11 +1,12 @@ /* - * getchar via ESTEX RST 10h. + * getchar — через ESTEX RST 10h. * - * ESTEX 0x30 (WAITKEY): blocks until a key, returns - * A = scan code, D = position code, E = ASCII, - * C = mode flags, B = shift flags. + * ESTEX 0x30 (WAITKEY): блокируется до нажатия клавиши, возвращает + * A = скан-код, D = позиционный код, E = ASCII, + * C = флаги режима, B = флаги шифтов. * - * IX is preserved (RST 10h clobbers it; callers rely on it as frame pointer). + * IX сохраняется (RST 10h его клобберит; для вызывающего это frame + * pointer). */ #include diff --git a/libc/stdio/hex16.c b/libc/stdio/hex16.c new file mode 100644 index 0000000..d40ca53 --- /dev/null +++ b/libc/stdio/hex16.c @@ -0,0 +1,22 @@ +/* + * hex16 — печать uint16 всегда четырьмя hex-цифрами ("0000".."FFFF"): + * два вызова hex8 (старший байт call, младший — tail-call jp). + * Кросс-модульные переходы по имени функции — работают через линкер. + */ + +#include +#include + +void hex16(uint16_t v) __naked +{ + (void)v; + __asm + ;; HL = v на входе. + ld a, h + push hl + call _hex8 + pop hl + ld a, l + jp _hex8 ; tail-call + __endasm; +} diff --git a/libc/stdio/hex32.c b/libc/stdio/hex32.c new file mode 100644 index 0000000..211ac08 --- /dev/null +++ b/libc/stdio/hex32.c @@ -0,0 +1,19 @@ +/* + * hex32 — печать uint32 всегда восемью hex-цифрами: два вызова hex16 + * (старшая половина call, младшая — tail-call jp). + */ + +#include +#include + +void hex32(uint32_t v) __naked +{ + (void)v; + __asm + ;; HL = старшие 16, DE = младшие 16 на входе (SDCC HLDE). + push de + call _hex16 + pop hl + jp _hex16 ; tail-call + __endasm; +} diff --git a/libc/stdio/hex8.c b/libc/stdio/hex8.c new file mode 100644 index 0000000..8beabc9 --- /dev/null +++ b/libc/stdio/hex8.c @@ -0,0 +1,35 @@ +/* + * hex8 — печать uint8 всегда двумя hex-цифрами ("00".."FF") через + * ESTEX PUTCHAR ($5B). Каждый нибл — классический трюк Z80 + * `cp 10 / sbc 0x69 / daa` (5 байт на нибл). Self-call на _hex8_digit + * для старшего нибла, затем проваливание на младший. + * + * ESTEX PUTCHAR сохраняет IX (проверено эмпирически) — push/pop ix + * вокруг RST не нужен. Портировано из solid-c STDLIB.ASM. + */ + +#include +#include + +void hex8(uint8_t v) __naked +{ + (void)v; + __asm + ;; A = v на входе. + push af + rra + rra + rra + rra + call _hex8_digit + pop af + _hex8_digit: + and a, #0x0F + cp a, #10 + sbc a, #0x69 + daa + ld c, #0x5B + rst #0x10 + ret + __endasm; +} diff --git a/libc/stdio/hex_print.c b/libc/stdio/hex_print.c deleted file mode 100644 index 70555e3..0000000 --- a/libc/stdio/hex_print.c +++ /dev/null @@ -1,68 +0,0 @@ -/* - * hex_print.c — compact hex print primitives ported from solid-c - * (third_party/solid-c/SRC/CLIB/STDLIB.ASM, modules hex8/hex16/hex32). - * - * void hex8 (uint8_t v) — always-2-digit "00" .. "FF" - * void hex16(uint16_t v) — always-4-digit "0000" .. "FFFF" - * void hex32(uint32_t v) — always-8-digit "00000000" .. "FFFFFFFF" - * - * Each nibble is emitted via the classic Z80 `cp 10 / sbc 0x69 / daa` - * trick — 5 bytes per nibble. hex8 self-calls for the high nibble - * then falls through for the low nibble. hex16/hex32 split into two - * hex8/hex16 calls. - * - * ESTEX PUTCHAR ($5B) preserves IX (empirically verified) so we skip - * the usual push/pop ix around the RST. - */ - -#include -#include - -void hex8(uint8_t v) __naked -{ - (void)v; - __asm - ;; A = v on entry. - push af - rra - rra - rra - rra - call _hex8_digit - pop af - _hex8_digit: - and a, #0x0F - cp a, #10 - sbc a, #0x69 - daa - ld c, #0x5B - rst #0x10 - ret - __endasm; -} - -void hex16(uint16_t v) __naked -{ - (void)v; - __asm - ;; HL = v on entry. - ld a, h - push hl - call _hex8 - pop hl - ld a, l - jp _hex8 ; tail-call - __endasm; -} - -void hex32(uint32_t v) __naked -{ - (void)v; - __asm - ;; HL = high16, DE = low16 on entry (SDCC HLDE). - push de - call _hex16 - pop hl - jp _hex16 ; tail-call - __endasm; -} diff --git a/libc/stdio/putchar.c b/libc/stdio/putchar.c index e4342d3..5c51329 100644 --- a/libc/stdio/putchar.c +++ b/libc/stdio/putchar.c @@ -1,16 +1,16 @@ /* - * putchar — emit one character via ESTEX PUTCHAR ($5B). + * putchar — вывести один символ через ESTEX PUTCHAR ($5B). * - * Turbo-C convention: this stdio.h function is the FAST path with NO - * attribute control. Whatever ESTEX has cached for the cursor cell is - * used (typically the shell's default colour). Translates '\n' to - * CR LF for C-string semantics. + * Конвенция Turbo-C: эта stdio.h-функция — БЫСТРЫЙ путь БЕЗ управления + * атрибутом. Используется то, что ESTEX закэшировал для ячейки курсора + * (обычно цвет шелла по умолчанию). '\n' транслируется в CR LF по + * семантике C-строк. * - * For coloured output, use putch() / cputs() / cprintf() from - * — those honour textattr / g_text_attr at the cost of being ~10× slower. + * Для цветного вывода — putch() / cputs() / cprintf() из : + * они учитывают textattr / g_text_attr ценой ~10-кратного замедления. * - * SDCC __sdcccall(1): char arg in L (low byte of HL=int). Returns the - * char in DE (SDCC int return). + * SDCC __sdcccall(1): char-аргумент в L (младший байт HL=int). + * Возврат символа в DE (int-возврат SDCC). */ #include diff --git a/libc/stdio/puts.c b/libc/stdio/puts.c index 330b2d5..f666321 100644 --- a/libc/stdio/puts.c +++ b/libc/stdio/puts.c @@ -1,39 +1,23 @@ /* - * puts — C99 fputs(s, stdout) + '\n'. + * puts — C99: вывести строку + '\n'. * - * Turbo-C convention: stdio's `puts` is the FAST path with NO attribute - * control — backed by ESTEX PCHARS ($5C). Cursor cell attributes are - * whatever ESTEX has cached (usually the shell's default). + * Конвенция Turbo-C: stdio-шный puts — БЫСТРЫЙ путь БЕЗ управления + * атрибутом; атрибуты ячеек — какие ESTEX закэшировал (обычно цвет + * шелла). Для цветного вывода — cputs() / cprintf() из . * - * For coloured output use cputs() / cprintf() from . - * - * Implementation notes: - * - PCHARS does NOT translate '\n' to CR LF, so we copy the string - * into a static buffer expanding each '\n' to CR LF, then append - * the trailing CR LF before the NUL. - * - Avoid trailing PUTCHAR after PCHARS — empirically that sometimes - * drops the next char. Embed the line ending inside the PCHARS - * buffer instead. + * Реализация: посимвольный цикл через putchar (он транслирует '\n' в + * CR LF), в конце — putchar('\n'). Быстрый путь через ESTEX PCHARS + * ($5C) не используется: PCHARS не транслирует '\n', а добивать хвост + * через PUTCHAR после PCHARS нельзя — эмпирически это иногда теряло + * следующий символ. */ #include #include -static void pchars(const char *s) __naked -{ - (void)s; - __asm - push ix - ld c, #0x5C - rst #0x10 - pop ix - ret - __endasm; -} - char puts(const char *s) __naked { - (void)s; + (void)s; __asm puts_: ld a, (hl) @@ -47,7 +31,7 @@ char puts(const char *s) __naked inc hl jp puts_ ; - fin_: + fin_: ld l, #0x0A ld h, #0 call _putchar diff --git a/libc/stdlib/max.c b/libc/stdlib/max.c new file mode 100644 index 0000000..519071d --- /dev/null +++ b/libc/stdlib/max.c @@ -0,0 +1,32 @@ +/* + * max — больший из двух int16_t. + * __sdcccall(1): a в HL, b в DE; возврат в DE. + * + * FIXME: сравнение через `cp` беззнаковое — для отрицательных + * аргументов результат неверный. Унаследовано из minmax.c до сплита; + * см. docs/libc-roadmap.md. + */ + +#include + +int16_t max(int16_t a, int16_t b) __naked +{ + (void)a; (void)b; + __asm + ld a, h + cp d + jr c, max_less + jr nz, max_greater + ld a, l + cp e + jr c, max_less + ret /* здесь h==d, l>=e — вернуть DE (при равенстве b==a) */ + max_less: + /* a < b, вернуть b в DE */ + ret /* DE уже содержит b */ + max_greater: + /* a > b, вернуть a */ + ex de, hl + ret + __endasm; +} diff --git a/libc/stdlib/min.c b/libc/stdlib/min.c new file mode 100644 index 0000000..650d315 --- /dev/null +++ b/libc/stdlib/min.c @@ -0,0 +1,29 @@ +/* + * min — меньший из двух int16_t. + * __sdcccall(1): a в HL, b в DE; возврат в DE. + * + * FIXME: сравнение через `cp` беззнаковое — для отрицательных + * аргументов результат неверный (min(-1,1) вернёт 1). Унаследовано + * из minmax.c до сплита; см. docs/libc-roadmap.md. + */ + +#include + +int16_t min(int16_t a, int16_t b) __naked +{ + (void)a; (void)b; + __asm + ld a, h + cp d + jr c, min_less + jr nz, min_greater + ld a, l + cp e + jr c, min_less + min_greater: + ret /* DE уже содержит b */ + min_less: + ex de, hl + ret + __endasm; +} diff --git a/libc/stdlib/minmax.c b/libc/stdlib/minmax.c deleted file mode 100644 index d2d2b80..0000000 --- a/libc/stdlib/minmax.c +++ /dev/null @@ -1,42 +0,0 @@ -#include - -int16_t min(int16_t a, int16_t b) __naked -{ - (void)a; (void)b; - __asm - ld a, h - cp d - jr c, min_less - jr nz, min_greater - ld a, l - cp e - jr c, min_less - min_greater: - ret /* DE уже содержит b */ - min_less: - ex de, hl - ret - __endasm; -} - -int16_t max(int16_t a, int16_t b) __naked -{ - (void)a; (void)b; - __asm - ld a, h - cp d - jr c, max_less - jr nz, max_greater - ld a, l - cp e - jr c, max_less - ret /* a >= b, return a in HL */ - max_less: - /* a < b, return b in DE */ - ret /* DE already contains b */ - max_greater: - /* a > b, return a in HL */ - ex de, hl - ret - __endasm; -} diff --git a/libc/string/strlwr.c b/libc/string/strlwr.c index 489a783..f48f345 100644 --- a/libc/string/strlwr.c +++ b/libc/string/strlwr.c @@ -1,10 +1,9 @@ /* - * solid_compat.c — Solid-C compatibility helpers that need real code - * (rather than just header macros). + * strlwr — строку в нижний регистр, латиница + кириллица CP866. * - * CP866 Cyrillic support: strlwr/strupr handle uppercase/lowercase - * conversion for both Latin and Cyrillic characters in CP866 code page - * (bytes 0x80–0xFF). + * CP866: А-Я = 0x80..0x8F (а-п = +0x20 даёт 0xA0..0xAF) и + * Р-Я = 0x90..0x9F (р-я = +0x50 даёт 0xE0..0xEF); Ё 0xF0 → ё 0xF1. + * Solid-C совместимость (). */ #include diff --git a/libc/string/strupr.c b/libc/string/strupr.c index 602b1e0..758d0e1 100644 --- a/libc/string/strupr.c +++ b/libc/string/strupr.c @@ -1,10 +1,9 @@ /* - * solid_compat.c — Solid-C compatibility helpers that need real code - * (rather than just header macros). + * strupr — строку в верхний регистр, латиница + кириллица CP866. * - * CP866 Cyrillic support: strlwr/strupr handle uppercase/lowercase - * conversion for both Latin and Cyrillic characters in CP866 code page - * (bytes 0x80–0xFF). + * CP866: а-п = 0xA0..0xAF (−0x20 даёт А-П 0x80..0x8F) и + * р-я = 0xE0..0xEF (−0x50 даёт Р-Я 0x90..0x9F); ё 0xF1 → Ё 0xF0. + * Solid-C совместимость (). */ #include diff --git a/libc/sys/_atexit.h b/libc/sys/_atexit.h new file mode 100644 index 0000000..c70e9ec --- /dev/null +++ b/libc/sys/_atexit.h @@ -0,0 +1,13 @@ +/* + * _atexit.h — внутренний стек atexit-хендлеров, общий для atexit() и + * exit(). НЕ публичный заголовок; данные живут в _atexit_state.c. + */ +#ifndef _ATEXIT_INTERNAL_H +#define _ATEXIT_INTERNAL_H + +#define ATEXIT_MAX 8 + +extern void (*_atexit_stack[ATEXIT_MAX])(void); +extern int _atexit_top; + +#endif diff --git a/libc/sys/_atexit_state.c b/libc/sys/_atexit_state.c new file mode 100644 index 0000000..7b334f2 --- /dev/null +++ b/libc/sys/_atexit_state.c @@ -0,0 +1,10 @@ +/* + * _atexit_state — стек зарегистрированных atexit-хендлеров (LIFO, + * максимум 8) и его вершина. Модуль только с данными; пишет atexit(), + * читает/сматывает exit(). + */ + +#include "_atexit.h" + +void (*_atexit_stack[ATEXIT_MAX])(void); +int _atexit_top; diff --git a/libc/sys/_exit.c b/libc/sys/_exit.c new file mode 100644 index 0000000..d849bb9 --- /dev/null +++ b/libc/sys/_exit.c @@ -0,0 +1,21 @@ +/* + * _exit — POSIX-сырое завершение: без цепочки atexit, сразу ESTEX EXIT + * ($41, B = код возврата). Не возвращается. + */ + +#include + +void _exit(int code) __naked +{ + (void)code; + __asm + ;; HL = code (единственный int-аргумент). + ld a, l + ld b, a + ld c, #0x41 ; ESTEX EXIT + rst #0x10 + ;; Возврата быть не должно. +1$: halt + jr 1$ + __endasm; +} diff --git a/libc/sys/atexit.c b/libc/sys/atexit.c index 04a4ae6..ee4d7ba 100644 --- a/libc/sys/atexit.c +++ b/libc/sys/atexit.c @@ -1,57 +1,21 @@ /* - * atexit + exit + _exit. + * atexit — зарегистрировать функцию, вызываемую при нормальном + * завершении (exit()), максимум 8, порядок LIFO. Возвращает 0 или -1 + * при переполнении стека хендлеров. * - * atexit(fn) — register fn to be called at normal termination (max 8). - * exit(code) — run the atexit chain in LIFO, then ESTEX EXIT. - * _exit(code) — POSIX raw exit: skip the chain, go straight to ESTEX. - * - * These functions own termination entirely; crt0.s only does inline RST - * 10h #41 when main returns without an explicit exit(). That path skips - * the atexit chain — programs that need handlers should call exit() at - * the bottom of main (or return through a wrapper). + * Внимание: возврат из main() без явного exit() уходит в inline RST + * 10h #41 в crt0.s и цепочку atexit НЕ выполняет — программам с + * хендлерами нужно завершаться через exit(). */ #include -#include - -#define ATEXIT_MAX 8 - -static void (*atexit_stack[ATEXIT_MAX])(void); -static int atexit_top = 0; +#include "_atexit.h" int atexit(void (*fn)(void)) { - if (atexit_top >= ATEXIT_MAX) { + if (_atexit_top >= ATEXIT_MAX) { return -1; } - atexit_stack[atexit_top++] = fn; + _atexit_stack[_atexit_top++] = fn; return 0; } - -/* exit() — runs the chain, then performs the raw ESTEX EXIT. */ -void exit(int code) -{ - while (atexit_top > 0) { - void (*fn)(void) = atexit_stack[--atexit_top]; - if (fn) { - fn(); - } - } - _exit(code); /* falls into the inline-asm raw exit below */ -} - -/* _exit() — POSIX raw termination, no atexit chain. */ -void _exit(int code) __naked -{ - (void)code; - __asm - ;; HL = code (single int arg). - ld a, l - ld b, a - ld c, #0x41 ; ESTEX EXIT - rst #0x10 - ;; Should not return. -1$: halt - jr 1$ - __endasm; -} diff --git a/libc/sys/exit.c b/libc/sys/exit.c new file mode 100644 index 0000000..a4c432b --- /dev/null +++ b/libc/sys/exit.c @@ -0,0 +1,19 @@ +/* + * exit — нормальное завершение: смотать цепочку atexit-хендлеров + * (LIFO), затем сырое завершение _exit() (ESTEX EXIT). + */ + +#include +#include +#include "_atexit.h" + +void exit(int code) +{ + while (_atexit_top > 0) { + void (*fn)(void) = _atexit_stack[--_atexit_top]; + if (fn) { + fn(); + } + } + _exit(code); +} diff --git a/libc/time/_time.h b/libc/time/_time.h new file mode 100644 index 0000000..ad6fe1e --- /dev/null +++ b/libc/time/_time.h @@ -0,0 +1,23 @@ +/* + * _time.h — внутренние календарные хелперы для time()/gmtime()/mktime(). + * + * НЕ публичный заголовок: живёт рядом с исходниками. Каждый хелпер — + * свой модуль (_tm_mdays.c/_tm_is_leap.c/_tm_year_days.c/_tm_month_days.c), + * чтобы приложение тянуло только те, которые реально вызывает. + */ +#ifndef _TIME_INTERNAL_H +#define _TIME_INTERNAL_H + +/* Дней в месяце невисокосного года (Jan..Dec). */ +extern const unsigned char _tm_mdays[12]; + +/* 1, если год високосный (по григорианскому календарю). */ +int _tm_is_leap(unsigned int y); + +/* Дней от 1970-01-01 до (y, 1, 1). */ +unsigned long _tm_year_days(unsigned int y); + +/* Дней от 1 января до начала месяца (месяц 1-based). */ +unsigned int _tm_month_days(unsigned int y, unsigned int m); + +#endif diff --git a/libc/time/_tm_is_leap.c b/libc/time/_tm_is_leap.c new file mode 100644 index 0000000..2054d40 --- /dev/null +++ b/libc/time/_tm_is_leap.c @@ -0,0 +1,8 @@ +/* _tm_is_leap — проверка високосности года (григорианский календарь). */ + +#include "_time.h" + +int _tm_is_leap(unsigned int y) +{ + return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); +} diff --git a/libc/time/_tm_mdays.c b/libc/time/_tm_mdays.c new file mode 100644 index 0000000..ab8290a --- /dev/null +++ b/libc/time/_tm_mdays.c @@ -0,0 +1,8 @@ +/* _tm_mdays — дней в месяце невисокосного года (Jan..Dec). Общая + * таблица для gmtime и _tm_month_days; модуль только с данными. */ + +#include "_time.h" + +const unsigned char _tm_mdays[12] = { + 31,28,31,30,31,30,31,31,30,31,30,31 +}; diff --git a/libc/time/_tm_month_days.c b/libc/time/_tm_month_days.c new file mode 100644 index 0000000..9a35311 --- /dev/null +++ b/libc/time/_tm_month_days.c @@ -0,0 +1,12 @@ +/* _tm_month_days — дней от 1 января до начала месяца (месяц 1-based), + * с учётом високосности. */ + +#include "_time.h" + +unsigned int _tm_month_days(unsigned int y, unsigned int m) +{ + unsigned int d = 0; + for (unsigned int i = 0; i < m - 1; i++) d += _tm_mdays[i]; + if (m > 2 && _tm_is_leap(y)) d++; + return d; +} diff --git a/libc/time/_tm_year_days.c b/libc/time/_tm_year_days.c new file mode 100644 index 0000000..6ba8b78 --- /dev/null +++ b/libc/time/_tm_year_days.c @@ -0,0 +1,11 @@ +/* _tm_year_days — дней от 1970-01-01 до 1 января года y. */ + +#include "_time.h" + +unsigned long _tm_year_days(unsigned int y) +{ + unsigned long d = 0; + for (unsigned int i = 1970; i < y; i++) + d += _tm_is_leap(i) ? 366 : 365; + return d; +} diff --git a/libc/time/asctime.c b/libc/time/asctime.c new file mode 100644 index 0000000..60329e7 --- /dev/null +++ b/libc/time/asctime.c @@ -0,0 +1,27 @@ +/* asctime — форматирует struct tm как "Day Mon DD HH:MM:SS YYYY\n" + * в статический буфер (перезаписывается следующим вызовом). */ + +#include +#include + +static const char *const dnames[7] = { + "Sun","Mon","Tue","Wed","Thu","Fri","Sat" +}; +static const char *const mnames[12] = { + "Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec" +}; + +/* "Day Mon DD HH:MM:SS YYYY\n" — 25 символов + NUL. */ +static char asctime_buf[26]; + +char *asctime(struct tm *tm) +{ + sprintf(asctime_buf, "%s %s %2d %02d:%02d:%02d %d\n", + dnames[tm->tm_wday % 7], + mnames[tm->tm_mon % 12], + tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, + tm->tm_year + 1900); + return asctime_buf; +} diff --git a/libc/time/ctime.c b/libc/time/ctime.c new file mode 100644 index 0000000..0b22554 --- /dev/null +++ b/libc/time/ctime.c @@ -0,0 +1,8 @@ +/* ctime — время эпохи одной строкой: asctime(localtime(timep)). */ + +#include + +char *ctime(time_t *timep) +{ + return asctime(localtime(timep)); +} diff --git a/libc/time/getdatetime.c b/libc/time/getdatetime.c new file mode 100644 index 0000000..7988e22 --- /dev/null +++ b/libc/time/getdatetime.c @@ -0,0 +1,49 @@ +/* + * getdatetime — чтение RTC через ESTEX SYSTIME ($21). + * + * $21 SYSTIME → D=day E=month IX=year H=hour L=min B=sec C=dow + * + * Раскладка структуры (datetime_t, 8 байт): + * +0 day, +1 month, +2..3 year, +4 hour, +5 min, +6 sec, +7 dow + * + * Пишет прямо в *dt — без статического скретча. Ключевой трюк — + * `ex (sp), hl` после RST: в HL час:мин, на вершине стека dt — + * один байт меняет их местами, дальше идём по структуре через HL. + */ + +#include + +void getdatetime(datetime_t *dt) __naked +{ + (void)dt; + __asm + push ix ; save caller IX (BIOS clobbers it) + push hl ; stash dt across RST (clobbers HL) + ld c, #0x21 ; ESTEX SYSTIME + rst #0x10 + ;; Returns: D=day E=month IX=year H=hour L=min B=sec C=dow + + ;; ex (sp), hl: TOS<->HL. Now HL = dt, TOS = hour:min stash. + ex (sp), hl + ld (hl), d ; +0 day + inc hl + ld (hl), e ; +1 month + inc hl + push ix ; year → stack + pop de ; DE = year + ld (hl), e ; +2 year low + inc hl + ld (hl), d ; +3 year high + inc hl + pop de ; D = hour, E = min (from earlier ex(sp)) + ld (hl), d ; +4 hour + inc hl + ld (hl), e ; +5 min + inc hl + ld (hl), b ; +6 sec + inc hl + ld (hl), c ; +7 dow + pop ix ; restore caller IX + ret + __endasm; +} diff --git a/libc/time/gmtime.c b/libc/time/gmtime.c new file mode 100644 index 0000000..5e8627b --- /dev/null +++ b/libc/time/gmtime.c @@ -0,0 +1,44 @@ +/* + * gmtime — разложить секунды эпохи в struct tm. + * + * localtime и gmtime делят один статический буфер — при необходимости + * пережить следующий вызов клиент копирует результат (поведение POSIX). + * Таймзон нет: localtime() просто вызывает gmtime(). + */ + +#include +#include "_time.h" + +static struct tm tm_buf; + +struct tm *gmtime(time_t *timep) +{ + unsigned long sec = *timep; + tm_buf.tm_sec = (unsigned char)(sec % 60); sec /= 60; + tm_buf.tm_min = (unsigned char)(sec % 60); sec /= 60; + tm_buf.tm_hour = (unsigned char)(sec % 24); sec /= 24; + /* sec теперь — дни с 1970-01-01 (четверг). */ + tm_buf.tm_wday = (unsigned char)((4 + sec) % 7); + /* подобрать год */ + unsigned int y = 1970; + unsigned long days = sec; + while (days >= (unsigned long)(_tm_is_leap(y) ? 366 : 365)) { + days -= _tm_is_leap(y) ? 366 : 365; + y++; + } + tm_buf.tm_year = (int)y - 1900; + tm_buf.tm_yday = (int)days; + /* подобрать месяц/день */ + unsigned int m = 0; + while (m < 12) { + unsigned int dim = _tm_mdays[m] + ((m == 1) && _tm_is_leap(y) ? 1u : 0u); + if (days < dim) break; + days -= dim; + m++; + } + tm_buf.tm_mon = (unsigned char)m; + tm_buf.tm_mday = (unsigned char)(days + 1); + tm_buf.tm_isdst = 0; + tm_buf.tm_hundredth = 0; + return &tm_buf; +} diff --git a/libc/time/localtime.c b/libc/time/localtime.c new file mode 100644 index 0000000..eede6a8 --- /dev/null +++ b/libc/time/localtime.c @@ -0,0 +1,8 @@ +/* localtime — идентичен gmtime: таймзоны на Sprinter не поддержаны. */ + +#include + +struct tm *localtime(time_t *timep) +{ + return gmtime(timep); +} diff --git a/libc/time/mktime.c b/libc/time/mktime.c new file mode 100644 index 0000000..577cf87 --- /dev/null +++ b/libc/time/mktime.c @@ -0,0 +1,22 @@ +/* mktime — struct tm → секунды Unix-эпохи (без таймзон/DST). + * Попутно заполняет tm_wday/tm_yday, чтобы клиент мог их читать. */ + +#include +#include "_time.h" + +time_t mktime(struct tm *tm) +{ + unsigned int y = (unsigned int)(tm->tm_year + 1900); + unsigned long days = _tm_year_days(y) + + _tm_month_days(y, (unsigned int)tm->tm_mon + 1) + + (unsigned int)(tm->tm_mday - 1); + time_t epoch = days * 86400UL + + (unsigned long)tm->tm_hour * 3600UL + + (unsigned long)tm->tm_min * 60UL + + tm->tm_sec; + /* Дозаполнить wday/yday для клиента. */ + tm->tm_wday = (unsigned char)((4 + days) % 7); + tm->tm_yday = (int)_tm_month_days(y, (unsigned int)tm->tm_mon + 1) + + (tm->tm_mday - 1); + return epoch; +} diff --git a/libc/time/posix_time.c b/libc/time/posix_time.c deleted file mode 100644 index f1696bf..0000000 --- a/libc/time/posix_time.c +++ /dev/null @@ -1,143 +0,0 @@ -/* - * posix_time.c — minimal POSIX implementation on top of - * getdatetime() (ESTEX SYSTIME $21). - * - * SDCC's z80.lib bundles time/localtime/mktime AND _RtcRead in a - * single time.rel module, so the user can't override _RtcRead from a - * separate object — overriding triggers a "multiple definition" - * linker error. We sidestep that by implementing the whole POSIX time - * API ourselves; the linker then never pulls SDCC's time.rel. - * - * The epoch is Unix (1970-01-01 00:00:00). No timezone support — - * gmtime and localtime are identical. No DST. - */ - -#include -#include - -static const unsigned char mdays[12] = { - 31,28,31,30,31,30,31,31,30,31,30,31 -}; - -static const char *const dnames[7] = { - "Sun","Mon","Tue","Wed","Thu","Fri","Sat" -}; -static const char *const mnames[12] = { - "Jan","Feb","Mar","Apr","May","Jun", - "Jul","Aug","Sep","Oct","Nov","Dec" -}; - -static int is_leap(unsigned int y) -{ - return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0); -} - -/* Days elapsed from 1970-01-01 to (y, 1, 1). */ -static unsigned long year_days(unsigned int y) -{ - unsigned long d = 0; - for (unsigned int i = 1970; i < y; i++) - d += is_leap(i) ? 366 : 365; - return d; -} - -/* Days from Jan 1 to month start (1-based month input). */ -static unsigned int month_days(unsigned int y, unsigned int m) -{ - unsigned int d = 0; - for (unsigned int i = 0; i < m - 1; i++) d += mdays[i]; - if (m > 2 && is_leap(y)) d++; - return d; -} - -time_t time(time_t *t) -{ - datetime_t dt; - getdatetime(&dt); - unsigned long days = year_days(dt.year) - + month_days(dt.year, dt.month) - + (dt.day - 1); - time_t epoch = days * 86400UL - + (unsigned long)dt.hour * 3600UL - + (unsigned long)dt.minute * 60UL - + dt.second; - if (t) *t = epoch; - return epoch; -} - -/* localtime and gmtime share one static buffer — caller copies if - * needed across further calls (matches POSIX behaviour). */ -static struct tm tm_buf; - -struct tm *gmtime(time_t *timep) -{ - unsigned long sec = *timep; - tm_buf.tm_sec = (unsigned char)(sec % 60); sec /= 60; - tm_buf.tm_min = (unsigned char)(sec % 60); sec /= 60; - tm_buf.tm_hour = (unsigned char)(sec % 24); sec /= 24; - /* sec is now days since 1970-01-01 (Thursday). */ - tm_buf.tm_wday = (unsigned char)((4 + sec) % 7); - /* find year */ - unsigned int y = 1970; - unsigned long days = sec; - while (days >= (unsigned long)(is_leap(y) ? 366 : 365)) { - days -= is_leap(y) ? 366 : 365; - y++; - } - tm_buf.tm_year = (int)y - 1900; - tm_buf.tm_yday = (int)days; - /* find month/day */ - unsigned int m = 0; - while (m < 12) { - unsigned int dim = mdays[m] + ((m == 1) && is_leap(y) ? 1u : 0u); - if (days < dim) break; - days -= dim; - m++; - } - tm_buf.tm_mon = (unsigned char)m; - tm_buf.tm_mday = (unsigned char)(days + 1); - tm_buf.tm_isdst = 0; - tm_buf.tm_hundredth = 0; - return &tm_buf; -} - -struct tm *localtime(time_t *timep) -{ - return gmtime(timep); /* no timezone */ -} - -time_t mktime(struct tm *tm) -{ - unsigned int y = (unsigned int)(tm->tm_year + 1900); - unsigned long days = year_days(y) - + month_days(y, (unsigned int)tm->tm_mon + 1) - + (unsigned int)(tm->tm_mday - 1); - time_t epoch = days * 86400UL - + (unsigned long)tm->tm_hour * 3600UL - + (unsigned long)tm->tm_min * 60UL - + tm->tm_sec; - /* Backfill wday/yday so callers can inspect them. */ - tm->tm_wday = (unsigned char)((4 + days) % 7); - tm->tm_yday = (int)month_days(y, (unsigned int)tm->tm_mon + 1) - + (tm->tm_mday - 1); - return epoch; -} - -/* "Day Mon DD HH:MM:SS YYYY\n" — 25 chars + NUL. */ -static char asctime_buf[26]; - -char *asctime(struct tm *tm) -{ - sprintf(asctime_buf, "%s %s %2d %02d:%02d:%02d %d\n", - dnames[tm->tm_wday % 7], - mnames[tm->tm_mon % 12], - tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec, - tm->tm_year + 1900); - return asctime_buf; -} - -char *ctime(time_t *timep) -{ - return asctime(localtime(timep)); -} diff --git a/libc/time/setdatetime.c b/libc/time/setdatetime.c new file mode 100644 index 0000000..b904366 --- /dev/null +++ b/libc/time/setdatetime.c @@ -0,0 +1,50 @@ +/* + * setdatetime — запись RTC через ESTEX SETTIME ($22). + * + * $22 SETTIME D=day E=month IX=year H=hour L=min B=sec → CF=err / A=errcode + * + * Читает прямо из *dt — без статического скретча. IX используется как + * указатель на структуру (перед RST перезагружается годом — так ждёт + * SETTIME). Возврат: 0 или -1 + errno. + */ + +#include +#include + +int setdatetime(const datetime_t *dt) __naked +{ + (void)dt; + __asm + push ix ; save caller IX + push hl + pop ix ; IX = dt + + ld d, (hl) ; +0 D = day + inc hl + ld e, (hl) ; +1 E = month + inc hl + ld c, (hl) ; +2 year low + inc hl + ld b, (hl) ; +3 year high (BC->IX) + inc hl + push bc + ld b, (hl) ; +4 H = hour + inc hl + ld c, (hl) ; +5 L = min (BC->HL) + inc hl + push bc + ld b, (hl) ; +6 B = sec + pop hl + pop ix + ld c, #0x22 ; ESTEX SETTIME + rst #0x10 + pop ix ; restore caller IX + jr c, _st_err2 + ld de, #0 + ret + _st_err2: + call __errno_set + ld de, #-1 + ret + __endasm; +} diff --git a/libc/time/sleep.c b/libc/time/sleep.c index a50da39..628c3ea 100644 --- a/libc/time/sleep.c +++ b/libc/time/sleep.c @@ -1,12 +1,12 @@ /* - * sleep — block for N seconds using the 50 Hz frame interrupt. + * sleep — заблокироваться на N секунд по кадровому прерыванию 50 Гц. * - * Sprinter ISR fires 50 times per second. `halt` parks the CPU until - * the next IRQ, so 50 halts = ~1 second of wall clock. This is the - * same trick solid-c uses in IO.ASM:285. + * ISR Sprinter срабатывает 50 раз в секунду. `halt` паркует CPU до + * следующего IRQ, так что 50 halt'ов = ~1 секунда стенного времени. + * Тот же трюк, что у solid-c в IO.ASM:285. * - * Note: requires interrupts to be enabled (they are by default — ESTEX - * sets up IM 1 with the frame ISR before our program runs). + * Замечание: требует включённых прерываний (по умолчанию включены — + * ESTEX настраивает IM 1 с кадровым ISR до запуска нашей программы). */ #include @@ -16,11 +16,11 @@ void sleep(unsigned int seconds) __naked (void)seconds; __asm inter: - ;; HL = seconds on entry (SDCC single int arg). + ;; HL = seconds на входе (единственный int-аргумент SDCC). ld a, h or a, l - ret Z ; sleep(0) — return immediately - ld b, #50 ; 50 halts per second (50 Hz interrupt) + ret Z ; sleep(0) — вернуться сразу + ld b, #50 ; 50 halt-ов на секунду (50 Гц) inner: halt djnz inner diff --git a/libc/time/time.c b/libc/time/time.c index 3f63217..03d129b 100644 --- a/libc/time/time.c +++ b/libc/time/time.c @@ -1,93 +1,30 @@ /* - * time.c — getdatetime / setdatetime via ESTEX SYSTIME ($21) / SETTIME ($22). + * time — текущие секунды Unix-эпохи из RTC (getdatetime / ESTEX + * SYSTIME $21). * - * $21 SYSTIME → D=day E=month IX=year H=hour L=min B=sec C=dow - * $22 SETTIME D=day E=month IX=year H=hour L=min B=sec → CF=err / A=errcode + * SDCC-шная z80.lib кладёт time/localtime/mktime И _RtcRead в один + * модуль time.rel, так что _RtcRead нельзя переопределить из + * отдельного объектника — будет "multiple definition". Обходим это, + * реализуя весь POSIX time API сами; тогда линкер никогда не тянет + * SDCC-шный time.rel. * - * Struct layout (datetime_t, 8 bytes): - * +0 day, +1 month, +2..3 year, +4 hour, +5 min, +6 sec, +7 dow - * - * Both routines write/read *dt directly — no static scratch. The key - * trick in getdatetime is `ex (sp), hl` after RST: HL holds hour:min, - * stack TOS holds dt — one byte swaps them, then we walk the struct - * via HL. setdatetime uses IX as the struct pointer (re-loaded with - * year just before RST since that's what SETTIME expects). + * Эпоха — Unix (1970-01-01 00:00:00). Без таймзон и DST. */ #include -#include +#include "_time.h" -void getdatetime(datetime_t *dt) __naked +time_t time(time_t *t) { - (void)dt; - __asm - push ix ; save caller IX (BIOS clobbers it) - push hl ; stash dt across RST (clobbers HL) - ld c, #0x21 ; ESTEX SYSTIME - rst #0x10 - ;; Returns: D=day E=month IX=year H=hour L=min B=sec C=dow - - ;; ex (sp), hl: TOS<->HL. Now HL = dt, TOS = hour:min stash. - ex (sp), hl - ld (hl), d ; +0 day - inc hl - ld (hl), e ; +1 month - inc hl - push ix ; year → stack - pop de ; DE = year - ld (hl), e ; +2 year low - inc hl - ld (hl), d ; +3 year high - inc hl - pop de ; D = hour, E = min (from earlier ex(sp)) - ld (hl), d ; +4 hour - inc hl - ld (hl), e ; +5 min - inc hl - ld (hl), b ; +6 sec - inc hl - ld (hl), c ; +7 dow - pop ix ; restore caller IX - ret - __endasm; -} - - - -int setdatetime(const datetime_t *dt) __naked -{ - (void)dt; - __asm - push ix ; save caller IX - push hl - pop ix ; IX = dt - - ld d, (hl) ; +0 D = day - inc hl - ld e, (hl) ; +1 E = month - inc hl - ld c, (hl) ; +2 year low - inc hl - ld b, (hl) ; +3 year high (BC->IX) - inc hl - push bc - ld b, (hl) ; +4 H = hour - inc hl - ld c, (hl) ; +5 L = min (BC->HL) - inc hl - push bc - ld b, (hl) ; +6 B = sec - pop hl - pop ix - ld c, #0x22 ; ESTEX SETTIME - rst #0x10 - pop ix ; restore caller IX - jr c, _st_err2 - ld de, #0 - ret - _st_err2: - call __errno_set - ld de, #-1 - ret - __endasm; + datetime_t dt; + getdatetime(&dt); + unsigned long days = _tm_year_days(dt.year) + + _tm_month_days(dt.year, dt.month) + + (dt.day - 1); + time_t epoch = days * 86400UL + + (unsigned long)dt.hour * 3600UL + + (unsigned long)dt.minute * 60UL + + dt.second; + if (t) *t = epoch; + return epoch; } diff --git a/libc/video/_pal_args.c b/libc/video/_pal_args.c new file mode 100644 index 0000000..74756dd --- /dev/null +++ b/libc/video/_pal_args.c @@ -0,0 +1,10 @@ +/* + * _pal_args — скретчи аргументов BIOS $A4 (только данные, см. _palette.h). + */ + +#include "_palette.h" + +uint8_t _pal_num; +uint8_t _pal_start; +uint8_t _pal_count; +uint16_t _pal_data; diff --git a/libc/video/_palette.h b/libc/video/_palette.h new file mode 100644 index 0000000..b3ab5f3 --- /dev/null +++ b/libc/video/_palette.h @@ -0,0 +1,23 @@ +/* + * _palette.h — внутренности палитровых модулей (НЕ публичный заголовок). + * + * Палитра Sprinter: BIOS $A4 PIC_SET_PAL / PIC_GET_PAL, $A6 SET_PAL_INIT. + * Направление в $A4 выбирает бит 7 регистра A: + * A = pal_num — запись из RAM в VRAM + * A = 0x80 | pal_num — чтение из VRAM в RAM + * pal_num 0..3 — графика, 4..7 — четыре текстовых плана. + */ +#ifndef _PALETTE_INTERNAL_H +#define _PALETTE_INTERNAL_H + +#include + +/* Скретчи для передачи аргументов в inline asm — SDCC __sdcccall(1) + * даёт только HL, остальное через память (данные в _pal_args.c; + * пишут pal_load и pal_get). */ +extern uint8_t _pal_num; +extern uint8_t _pal_start; +extern uint8_t _pal_count; +extern uint16_t _pal_data; + +#endif diff --git a/libc/video/_videomode_raw_get.c b/libc/video/_videomode_raw_get.c new file mode 100644 index 0000000..e6b5068 --- /dev/null +++ b/libc/video/_videomode_raw_get.c @@ -0,0 +1,20 @@ +/* + * _videomode_raw_get — текущий видеорежим (ESTEX GETVMOD $51), + * БЕЗ валидации класса режима. Используется и conio (публичный + * gettextmode), и gfx (gfx_init) — свой модуль, чтобы чисто + * графическая программа не тянула conio ради номера режима. + */ + +#include + +uint8_t _videomode_raw_get(void) __naked +{ + __asm + push ix + ld c, #0x51 ; ESTEX GETVMOD + rst #0x10 + pop ix + ;; uint8_t возвращается в A — ESTEX уже положил режим туда. + ret + __endasm; +} diff --git a/libc/video/_videomode_raw_set.c b/libc/video/_videomode_raw_set.c new file mode 100644 index 0000000..9b20c3e --- /dev/null +++ b/libc/video/_videomode_raw_set.c @@ -0,0 +1,30 @@ +/* + * _videomode_raw_set — установить видеорежим (ESTEX SETVMOD $50), + * БЕЗ валидации класса режима. Публичный settextmode (conio) + * оборачивает его text-only проверкой; gfx_init/gfx_done зовут + * напрямую, чтобы входить В графические режимы. + * Возврат: 0 или -1 (errno из кода ESTEX). + */ + +#include +#include + +int _videomode_raw_set(uint8_t mode) __naked +{ + (void)mode; + __asm + ;; SDCC __sdcccall(1): единственный uint8_t уже в A. + push ix + ld bc, #0x0050 ; ESTEX SETVMOD (B=0 (страница), C=0x50) + rst #0x10 + jr c, _vmr_err + ld de, #0 + pop ix + ret + _vmr_err: + call __errno_set + ld de, #-1 + pop ix + ret + __endasm; +} diff --git a/libc/video/pal_get.c b/libc/video/pal_get.c new file mode 100644 index 0000000..8cda7a1 --- /dev/null +++ b/libc/video/pal_get.c @@ -0,0 +1,28 @@ +/* + * pal_get — прочитать блок палитровых записей из VRAM (BIOS $A4; + * тот же id функции, бит 7 регистра A переключает в чтение). + */ + +#include "_palette.h" + +void pal_get(uint8_t pal_num, uint8_t start, uint8_t count, uint8_t *bgr0) +{ + _pal_num = (uint8_t)(pal_num | 0x80); /* бит 7 = чтение */ + _pal_start = start; + _pal_count = count; + _pal_data = (uint16_t)(uintptr_t)bgr0; + + __asm + push ix + ld a, (__pal_start) + ld e, a ; E = start + ld a, (__pal_count) + ld d, a ; D = count (0 = 256) + ld hl, (__pal_data) ; HL = буфер + ld b, #0xFF ; B = маска + ld a, (__pal_num) ; A = 0x80 | pal_num — чтение + ld c, #0xA4 ; BIOS PIC_GET_PAL (= $A4) + rst #0x08 + pop ix + __endasm; +} diff --git a/libc/video/pal_get_color.c b/libc/video/pal_get_color.c new file mode 100644 index 0000000..d201ddd --- /dev/null +++ b/libc/video/pal_get_color.c @@ -0,0 +1,16 @@ +/* + * pal_get_color — прочитать одну палитровую запись в R/G/B + * (любой указатель может быть NULL). + */ + +#include "_palette.h" + +void pal_get_color(uint8_t pal_num, uint8_t slot, + uint8_t *r, uint8_t *g, uint8_t *b) +{ + uint8_t entry[4]; + pal_get(pal_num, slot, 1, entry); + if (b) *b = entry[0]; + if (g) *g = entry[1]; + if (r) *r = entry[2]; +} diff --git a/libc/video/pal_load.c b/libc/video/pal_load.c new file mode 100644 index 0000000..ed0db36 --- /dev/null +++ b/libc/video/pal_load.c @@ -0,0 +1,30 @@ +/* + * pal_load — записать блок палитровых записей в VRAM + * (BIOS $A4 PIC_SET_PAL): pal_num 0..7, start — первый слот, + * count слотов (0 = 256), bgr0 — записи по 4 байта (B, G, R, 0). + */ + +#include "_palette.h" + +void pal_load(uint8_t pal_num, uint8_t start, uint8_t count, + const uint8_t *bgr0) +{ + _pal_num = pal_num; + _pal_start = start; + _pal_count = count; + _pal_data = (uint16_t)(uintptr_t)bgr0; + + __asm + push ix + ld a, (__pal_start) + ld e, a ; E = start + ld a, (__pal_count) + ld d, a ; D = count (0 = 256) + ld hl, (__pal_data) ; HL = данные + ld b, #0xFF ; B = маска (без AND) + ld a, (__pal_num) ; A = номер палитры (бит 7 = 0: запись) + ld c, #0xA4 ; BIOS PIC_SET_PAL + rst #0x08 + pop ix + __endasm; +} diff --git a/libc/video/pal_reset.c b/libc/video/pal_reset.c new file mode 100644 index 0000000..aeaf625 --- /dev/null +++ b/libc/video/pal_reset.c @@ -0,0 +1,14 @@ +/* + * pal_reset — восстановить палитру по умолчанию типа `type` во всех + * четырёх текстовых палитровых страницах (4..7). + */ + +#include "_palette.h" + +void pal_reset(uint8_t type) +{ + pal_reset_at(type, 4, 0); + pal_reset_at(type, 5, 0); + pal_reset_at(type, 6, 0); + pal_reset_at(type, 7, 0); +} diff --git a/libc/video/pal_reset_at.c b/libc/video/pal_reset_at.c new file mode 100644 index 0000000..afb732f --- /dev/null +++ b/libc/video/pal_reset_at.c @@ -0,0 +1,30 @@ +/* + * pal_reset_at — восстановить встроенную палитру по умолчанию + * (BIOS $A6 SET_PAL_INIT): A = pal_page, E = графическая палитра + * (0..3), B = тип — PAL_GRAPH (1) / PAL_SINCLAIR (2) / PAL_CGA (3). + */ + +#include "_palette.h" + +static uint8_t reset_page_; +static uint8_t reset_graph_; +static uint8_t reset_type_; + +void pal_reset_at(uint8_t type, uint8_t pal_page, uint8_t graph_pal) +{ + reset_type_ = type; + reset_page_ = pal_page; + reset_graph_ = graph_pal; + + __asm + push ix + ld a, (_reset_graph_) + ld e, a ; E = индекс графической палитры (0..3) + ld a, (_reset_type_) + ld b, a ; B = тип (1=GRAPH, 2=SINCLAIR, 3=CGA) + ld a, (_reset_page_) ; A = палитровая страница (последним — A занят) + ld c, #0xA6 ; BIOS SET_PAL_INIT + rst #0x08 + pop ix + __endasm; +} diff --git a/libc/video/pal_set_color.c b/libc/video/pal_set_color.c new file mode 100644 index 0000000..58a93c6 --- /dev/null +++ b/libc/video/pal_set_color.c @@ -0,0 +1,17 @@ +/* + * pal_set_color — одна палитровая запись из RGB: собирает четвёрку + * (B, G, R, 0) и зовёт pal_load(pal_num, slot, 1, ...). + */ + +#include "_palette.h" + +void pal_set_color(uint8_t pal_num, uint8_t slot, + uint8_t r, uint8_t g, uint8_t b) +{ + uint8_t entry[4]; + entry[0] = b; + entry[1] = g; + entry[2] = r; + entry[3] = 0; + pal_load(pal_num, slot, 1, entry); +} diff --git a/libc/video/palette.c b/libc/video/palette.c deleted file mode 100644 index 23116d1..0000000 --- a/libc/video/palette.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * palette.c — Sprinter palette (BIOS $A4 PIC_SET_PAL / PIC_GET_PAL, - * BIOS $A6 SET_PAL_INIT). - * - * Direction in $A4 is selected by bit 7 of A: - * A = pal_num → write entries from RAM to VRAM - * A = 0x80 | pal_num → read entries from VRAM into RAM - * - * Lives under libc/video/ because palette control is shared between - * graphics (mode 0x81/0x82) and text (mode 0x03) — pal_num 0..3 for - * graphics, 4..7 for the four text-mode planes. The conio and gfx - * subsystems each ship thin domain-specific wrappers on top. - */ - -#include -#include - -/* Statics used to pass parameters to the inline asm — SDCC __sdcccall(1) - * gives us only HL natively, so the rest go through memory. */ -static uint8_t pal_num_; -static uint8_t pal_start_; -static uint8_t pal_count_; -static uint16_t pal_data_; - -/* ---- $A4 PIC_SET_PAL — write entries to VRAM --------------------- */ - -void pal_load(uint8_t pal_num, uint8_t start, uint8_t count, - const uint8_t *bgr0) -{ - pal_num_ = pal_num; - pal_start_ = start; - pal_count_ = count; - pal_data_ = (uint16_t)(uintptr_t)bgr0; - - __asm - push ix - ld a, (_pal_start_) - ld e, a ; E = start - ld a, (_pal_count_) - ld d, a ; D = count (0 → 256) - ld hl, (_pal_data_) ; HL = data - ld b, #0xFF ; B = mask (no AND) - ld a, (_pal_num_) ; A = palette number (bit7 = 0 → write) - ld c, #0xA4 ; BIOS PIC_SET_PAL - rst #0x08 - pop ix - __endasm; -} - -/* ---- $A4 PIC_GET_PAL — read entries from VRAM -------------------- * - * Same function id; bit 7 of A flips it to read mode. */ - -void pal_get(uint8_t pal_num, uint8_t start, uint8_t count, uint8_t *bgr0) -{ - pal_num_ = (uint8_t)(pal_num | 0x80); /* bit 7 = read */ - pal_start_ = start; - pal_count_ = count; - pal_data_ = (uint16_t)(uintptr_t)bgr0; - - __asm - push ix - ld a, (_pal_start_) - ld e, a ; E = start - ld a, (_pal_count_) - ld d, a ; D = count (0 → 256) - ld hl, (_pal_data_) ; HL = buffer - ld b, #0xFF ; B = mask - ld a, (_pal_num_) ; A = 0x80 | pal_num → read - ld c, #0xA4 ; BIOS PIC_GET_PAL (= $A4) - rst #0x08 - pop ix - __endasm; -} - -/* ---- one-colour helpers ------------------------------------------ */ - -void pal_set_color(uint8_t pal_num, uint8_t slot, - uint8_t r, uint8_t g, uint8_t b) -{ - uint8_t entry[4]; - entry[0] = b; - entry[1] = g; - entry[2] = r; - entry[3] = 0; - pal_load(pal_num, slot, 1, entry); -} - -void pal_get_color(uint8_t pal_num, uint8_t slot, - uint8_t *r, uint8_t *g, uint8_t *b) -{ - uint8_t entry[4]; - pal_get(pal_num, slot, 1, entry); - if (b) *b = entry[0]; - if (g) *g = entry[1]; - if (r) *r = entry[2]; -} - -/* ---- $A6 SET_PAL_INIT — restore a built-in default palette ------- * - * Signature: A = pal_page, E = graphics palette index (0..3), B = type. - * Type is one of PAL_GRAPH (1) / PAL_SINCLAIR (2) / PAL_CGA (3). */ - -static uint8_t reset_page_; -static uint8_t reset_graph_; -static uint8_t reset_type_; - -void pal_reset_at(uint8_t type, uint8_t pal_page, uint8_t graph_pal) -{ - reset_type_ = type; - reset_page_ = pal_page; - reset_graph_ = graph_pal; - - __asm - push ix - ld a, (_reset_graph_) - ld e, a ; E = graphics palette index (0..3) - ld a, (_reset_type_) - ld b, a ; B = type (1=GRAPH, 2=SINCLAIR, 3=CGA) - ld a, (_reset_page_) ; A = palette page (last — A is needed) - ld c, #0xA6 ; BIOS SET_PAL_INIT - rst #0x08 - pop ix - __endasm; -} - -void pal_reset(uint8_t type) -{ - pal_reset_at(type, 4, 0); - pal_reset_at(type, 5, 0); - pal_reset_at(type, 6, 0); - pal_reset_at(type, 7, 0); -} diff --git a/libc/video/videomode_raw.c b/libc/video/videomode_raw.c deleted file mode 100644 index b524aab..0000000 --- a/libc/video/videomode_raw.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * videomode_raw.c — low-level ESTEX SETVMOD / GETVMOD ($50 / $51). - * - * Plain getters/setters with NO mode-class validation. Used by both - * conio (text-validated public API) and gfx (graphics modes). Lives - * in its own .c so a pure graphics program does not pull in the entire - * conio module to switch modes. - * - * Public conio functions in conio.c wrap these with a text-mode check; - * gfx_init / gfx_done in gfx_core.c call them directly. - */ - -#include -#include - -uint8_t _videomode_raw_get(void) __naked -{ - __asm - push ix - ld c, #0x51 ; ESTEX GETVMOD - rst #0x10 - pop ix - ;; uint8_t returns in A — ESTEX already put mode there. - ret - __endasm; -} - -int _videomode_raw_set(uint8_t mode) __naked -{ - (void)mode; - __asm - ;; SDCC __sdcccall(1) passes uint8_t in A — leave it there. - push ix - ld bc, #0x0050 ; ESTEX SETVMOD (B=0 (page), C=0x50) - rst #0x10 - jr c, _vmr_err - ld de, #0 - pop ix - ret - _vmr_err: - call __errno_set - ld de, #-1 - pop ix - ret - __endasm; -} diff --git a/tests/conio/conio.c b/tests/conio/conio.c index 96b925c..8ea68a0 100644 --- a/tests/conio/conio.c +++ b/tests/conio/conio.c @@ -105,7 +105,7 @@ int main(void) cputs("All conio functions exercised. Press any key to exit."); - set_videotextmode(TEXT_MODE_40x32); + settextmode(TEXT_MODE_40x32); for (int i = 0; i < 16; i++) { wrchar(4 + i * 2, 3, i < 10 ? '0' + i : 'A' - 10 + i, 0x1F); @@ -122,7 +122,7 @@ int main(void) (void)getch(); - set_videotextmode(TEXT_MODE_80x32); + settextmode(TEXT_MODE_80x32); return 0; } diff --git a/tests/hello/hello.c b/tests/hello/hello.c index 7aa8fb5..445ef17 100644 --- a/tests/hello/hello.c +++ b/tests/hello/hello.c @@ -20,8 +20,8 @@ int main(void) int16_t a0 = 0, a1 = 0; a0 = get_text_attr(); - uint8_t vMode = get_videotextmode(); - set_videotextmode(TEXT_MODE_80x32); + uint8_t vMode = gettextmode(); + settextmode(TEXT_MODE_80x32); /* Wipe the screen with a known attribute so stdio printf/puts inherit * a clean colour for the cursor. */ @@ -60,6 +60,6 @@ int main(void) cputs("Press any key to exit..."); (void)getchar(); - set_videotextmode(vMode); + settextmode(vMode); return 0; } diff --git a/tests/text_palette/text_palette.c b/tests/text_palette/text_palette.c index a290131..15d819f 100644 --- a/tests/text_palette/text_palette.c +++ b/tests/text_palette/text_palette.c @@ -116,8 +116,8 @@ static void wait_key(const char *prompt) int main(void) { - uint8_t prev_mode = get_videotextmode(); - set_videotextmode(TEXT_MODE_80x32); + uint8_t prev_mode = gettextmode(); + settextmode(TEXT_MODE_80x32); textattr(ATTR_DEFAULT); /* ----- (1) Initial CGA palette ---------------------------------- */ @@ -155,6 +155,6 @@ int main(void) reset_all_text_planes(PAL_CGA); textattr(ATTR_DEFAULT); clrscr(); - set_videotextmode(prev_mode); + settextmode(prev_mode); return 0; }