conio: switch cursor get/set to BIOS calls, rename text-mode accessors

_get_cursor/_set_cursor now use BIOS GetCursor/SetCursor (RST 8, 08Eh/084h)
instead of ESTEX CURSOR/LOCATE; cursor position packed into a two_bytes
union. get_videotextmode/set_videotextmode renamed to gettextmode/settextmode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 17:07:16 +03:00
parent 016fedd94c
commit 5e5e70d0c8
3 changed files with 177 additions and 156 deletions
+133 -146
View File
@@ -15,10 +15,18 @@
#include <stdint.h> #include <stdint.h>
#include <errno.h> #include <errno.h>
/* Forward extern — definition is further down (after putch/cputs which /* Forward extern — definition is further down (after putch/cputs which
* reference it from asm by linker-symbol name). */ * reference it from asm by linker-symbol name). */
extern int16_t g_text_attr; 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 char kbhit(void) __naked
{ {
__asm __asm
@@ -91,10 +99,6 @@ uint16_t getkey(void) __naked
* caller to use "\r\n" explicitly. Stdio puts/putchar do translate. * caller to use "\r\n" explicitly. Stdio puts/putchar do translate.
*/ */
static uint8_t pc_ch = 0;
static uint8_t pc_attr = 0;
static uint8_t pc_row = 0;
static uint8_t pc_col = 0;
/* Controls how _raw_putch treats control characters (< 0x20): /* Controls how _raw_putch treats control characters (< 0x20):
* 0 (default) — BS/TAB/LF/CR are interpreted (no glyph output); * 0 (default) — BS/TAB/LF/CR are interpreted (no glyph output);
@@ -104,7 +108,6 @@ static uint8_t pc_col = 0;
* Only takes effect on the WRCHAR (attr ≤ 0xFF) path. When * Only takes effect on the WRCHAR (attr ≤ 0xFF) path. When
* g_text_attr is KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS handle * g_text_attr is KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS handle
* cursor and control chars — pc_raw_mode is irrelevant. */ * cursor and control chars — pc_raw_mode is irrelevant. */
static uint8_t pc_raw_mode = 0;
void set_putch_raw_mode(uint8_t mode) { pc_raw_mode = mode; } void set_putch_raw_mode(uint8_t mode) { pc_raw_mode = mode; }
uint8_t get_putch_raw_mode(void) { return pc_raw_mode; } uint8_t get_putch_raw_mode(void) { return pc_raw_mode; }
@@ -115,14 +118,11 @@ uint8_t get_putch_raw_mode(void) { return pc_raw_mode; }
static void _get_cursor(void) __naked static void _get_cursor(void) __naked
{ {
__asm __asm
push ix ; push ix
ld c, #0x53 ; ESTEX CURSOR ld c, #0x8e ; BIOS GetCursor
rst #0x10 rst #0x08
ld a, d ld (_pc_place), de
ld (_pc_row), a ; pop ix
ld a, e
ld (_pc_col), a
pop ix
ret ret
__endasm; __endasm;
} }
@@ -131,14 +131,11 @@ static void _get_cursor(void) __naked
static void _set_cursor(void) __naked static void _set_cursor(void) __naked
{ {
__asm __asm
push ix ; push ix
ld a, (_pc_row) ld de, (_pc_place)
ld d, a ld c, #0x84 ; BIOS SetCursor
ld a, (_pc_col) rst #0x08
ld e, a ; pop ix
ld c, #0x52 ; ESTEX LOCATE
rst #0x10
pop ix
ret ret
__endasm; __endasm;
} }
@@ -183,7 +180,7 @@ static char _bios_putchar(char ch) __naked
* once before a sequence of _raw_putch calls and write it back once * 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. */ * 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. */ /* Mode-0 worker: interprets BS/TAB/LF/CR, outputs other chars as glyphs. */
static void _raw_putch_raw0(char ch, uint8_t attr) __naked static void _putch_wrchar(char ch, uint8_t attr) __naked
{ {
(void)ch; (void)attr; (void)ch; (void)attr;
__asm __asm
@@ -191,6 +188,12 @@ static void _raw_putch_raw0(char ch, uint8_t attr) __naked
;; Dispatch on control chars while A still holds ch (cp does not ;; 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 ;; modify A). B/C only get loaded on the output path so the
;; ctrl-char paths are cheaper. ;; 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 cp #0x08
jr z, _rp0_bs jr z, _rp0_bs
cp #0x09 cp #0x09
@@ -201,18 +204,21 @@ static void _raw_putch_raw0(char ch, uint8_t attr) __naked
jr z, _rp0_cr jr z, _rp0_cr
;; Anything else (printable or unrecognised ctrl) glyph. ;; Anything else (printable or unrecognised ctrl) glyph.
_rp0_pri:
ld c, a ; C = ch (save before A is clobbered) ld c, a ; C = ch (save before A is clobbered)
ld a, (_pc_row) ld a, (_pc_place + 1)
cp #32 cp #32
ret nc ; off-screen bottom silently skip ret nc ; off-screen bottom silently skip
ld d, a ; D = row (ESTEX WRCHAR convention) ld d, a ; D = row (ESTEX WRCHAR convention)
ld a, (_pc_col) ld a, (_pc_place)
cp #80 cp #80
ret nc ; off-screen right silently skip ret nc ; off-screen right silently skip
ld e, a ; E = col ld e, a ; E = col
inc a inc a
ld (_pc_col), a ; pc_col++ ld (_pc_place), a ; pc_col++
_rp0_wr:
ld b, l ; B = attr ld b, l ; B = attr
ld a, c ; A = ch ld a, c ; A = ch
push ix push ix
@@ -222,56 +228,61 @@ static void _raw_putch_raw0(char ch, uint8_t attr) __naked
ret ret
_rp0_bs: _rp0_bs:
ld a, (_pc_col) ld a, (_pc_place)
or a, a or a, a
ret z ; already at col 0 no change ret z ; already at col 0 no change
dec a dec a
ld (_pc_col), a ld (_pc_place), a
ret 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: _rp0_tab:
ld a, (_pc_col) ld a, (_pc_place)
and #0xF8 ; floor to mult of 8 or #0x07 ; floor to mult of 8
add a, #8 ; next mult of 8 inc a ; next mult of 8
cp #80 cp #81 ; сравнить A с 81 (0x51)
jr c, _rp0_tab_store jr c, _rp0_tab_skip ; если A < 81 (т.е. A 80), пропустить загрузку
ld a, #80 ; cap at off-screen right ld a, #80 ; иначе A > 80 установить A = 80
_rp0_tab_store: _rp0_tab_skip:
ld (_pc_col), a ld (_pc_place), a
ret ret
_rp0_lf: _rp0_lf:
ld a, (_pc_row) ld a, (_pc_place + 1)
cp #32 cp #32
ret nc ; already at bottom edge ret nc ; already at bottom edge
inc a inc a
ld (_pc_row), a ld (_pc_place + 1), a
ret ret
_rp0_cr: _rp0_cr:
xor a, a xor a, a
ld (_pc_col), a ld (_pc_place), a
ret ret
__endasm; __endasm;
} }
/* Mode-1 worker: every byte goes through WRCHAR as a glyph. */ /* Mode-1 worker: every byte goes through WRCHAR as a glyph. */
static void _raw_putch_raw1(char ch, uint8_t attr) __naked static void _putch_wrchar_raw(char ch, uint8_t attr) __naked
{ {
(void)ch; (void)attr; (void)ch; (void)attr;
__asm __asm
;; __sdcccall(1): ch in A, attr in L. ;; __sdcccall(1): ch in A, attr in L.
ld c, a ; C = ch (save) ld c, a ; C = ch (save)
ld a, (_pc_row)
ld a, (_pc_place + 1)
cp #32 cp #32
ret nc ; off-screen bottom silently skip ret nc ; off-screen bottom silently skip
ld d, a ; D = row ld d, a ; D = row
ld a, (_pc_col) ld a, (_pc_place)
cp #80 cp #80
ret nc ; off-screen right silently skip ret nc ; off-screen right silently skip
ld e, a ; E = col ld e, a ; E = col
inc a inc a
ld (_pc_col), a ; pc_col++ ld (_pc_place), a ; pc_col++
ld b, l ; B = attr ld b, l ; B = attr
ld a, c ; A = ch ld a, c ; A = ch
@@ -283,18 +294,6 @@ static void _raw_putch_raw1(char ch, uint8_t attr) __naked
__endasm; __endasm;
} }
/* PCHARS (no attr) — used by cputs when KEEP_EXIST_ATTR is in effect. */
static void _cputs_pchars(const char *s) __naked
{
(void)s;
__asm
push ix
ld c, #0x5C ; ESTEX PCHARS
rst #0x10
pop ix
ret
__endasm;
}
/* ---- Public putch / cputs --------------------------------------- * /* ---- Public putch / cputs --------------------------------------- *
* *
@@ -306,6 +305,7 @@ static void _cputs_pchars(const char *s) __naked
* folds the per-char CURSOR/LOCATE pair from the old design into a * folds the per-char CURSOR/LOCATE pair from the old design into a
* single pair per operation. */ * single pair per operation. */
char putch(char ch) __naked char putch(char ch) __naked
{ {
(void)ch; (void)ch;
@@ -313,38 +313,26 @@ char putch(char ch) __naked
;; A = ch on entry; char return A. ;; A = ch on entry; char return A.
ld (_pc_ch), a ; stash c (for both return and re-load) ld (_pc_ch), a ; stash c (for both return and re-load)
;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0
ld a, (_g_text_attr + 1)
or a, a
jr nz, _putch_fast
;; --- WRCHAR path: cursor worker cursor ---
call __get_cursor
;; Worker ABI (2 char/uint8 args): ch in A, attr in L.
ld a, (_g_text_attr) ; A = low byte = attr ld a, (_g_text_attr) ; A = low byte = attr
ld l, a ; L = attr ld l, a ; L = attr
ld a, (_pc_raw_mode) ld a, (_pc_raw_mode)
or a, a ; Z = (mode == 0) or a, a ; Z = (mode == 0)
ld a, (_pc_ch) ; A = ch (`ld a,(nn)` does not touch flags) ld a, (_pc_ch) ; A = ch (`ld a,(nn)` does not touch flags)
jr nz, _putch_use_raw1 jr nz, _putch_use_raw
call __raw_putch_raw0 call __putch_wrchar
jr _putch_after_raw jr _putch_after_raw
_putch_use_raw1: _putch_use_raw:
call __raw_putch_raw1 call __putch_wrchar_raw
_putch_after_raw: _putch_after_raw:
call __set_cursor call __set_cursor
ld a, (_pc_ch) ; return value ld a, (_pc_ch) ; return value
ret ret
_putch_fast:
ld a, (_pc_ch)
call __bios_putchar ; __bios_putchar keeps AF
ret
__endasm; __endasm;
} }
char cputs(const char *s) __naked char cputs(const char *s) __naked
{ {
(void)s; (void)s;
@@ -356,60 +344,48 @@ char cputs(const char *s) __naked
or a, l or a, l
ret z ret z
;; IX is callee-saved under SDCC's z80 ABI. We use it as a
;; function pointer below (ld ix, #__raw_putch_raw0/1) so save
;; the caller's value up front and restore before every ret.
push ix push ix
call __get_cursor
;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0 ;; KEEP_EXIST_ATTR? high byte of g_text_attr != 0
ld a, (_g_text_attr + 1) ld a, (_g_text_attr + 1)
or a, a or a, a
jr nz, _cputs_fast jr nz, _cputs_fast
;; --- WRCHAR path: cursor worker cursor ---
call __get_cursor
;; Worker walks the string via DE; HL is used to carry attr in L
;; across iterations (RST 10 inside worker clobbers it, so we
;; push/pop hl around each call).
ex de, hl ; DE = s
ld a, (_g_text_attr) ; A = attr (low byte)
ld l, a ; L = attr (worker ABI: arg2 in L)
;; Pick worker once based on pc_raw_mode; IX = function pointer.
ld a, (_pc_raw_mode) ld a, (_pc_raw_mode)
or a, a or a, a
jr z, _cputs_use_raw0 jr nz, _cputs_bios
ld ix, #__raw_putch_raw1
jr _cputs_loop
_cputs_use_raw0:
ld ix, #__raw_putch_raw0
_cputs_loop: push hl
ld a, (de)
or a, a
jr z, _cputs_loop_end
inc de
;; Z80 has no "call (ix)" emulate via push-of-ret + jp (ix).
push de ; save string pointer
push hl ; save attr (in L)
ld de, #_cputs_after_worker
push de ; push return address
jp (ix) ; "call" worker
_cputs_after_worker:
pop hl
pop de pop de
jr _cputs_loop
_cputs_loop_end:
call __set_cursor _cputs_wrloop:
pop ix ; restore caller's IX ld a, (_g_text_attr)
xor a, a ; return 0 ld l, a
ret 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: _cputs_fast:
call __cputs_pchars ld c, #0x5C ; ESTEX PCHARS
pop ix ; restore caller's IX rst #0x10
_cputs_ex:
call __set_cursor
pop ix ; restore callers IX
xor a, a ; return 0 xor a, a ; return 0
ret ret
__endasm; __endasm;
@@ -446,12 +422,15 @@ void gotoxy(uint8_t x, uint8_t y) __naked
__asm __asm
;; __sdcccall(1) 2 uint8 args: x in A, y in L. ;; __sdcccall(1) 2 uint8 args: x in A, y in L.
;; ESTEX LOCATE ($52) wants: D = row, E = col. ;; ESTEX LOCATE ($52) wants: D = row, E = col.
push ix ;; push ix
ld d, l ; D = row (y) ld d, l ; D = row (y)
ld e, a ; E = col (x) ld e, a ; E = col (x)
ld c, #0x52 ld (_pc_place), de
rst #0x10 ld c, #0x84 ; BIOS SetCursor
pop ix rst #0x08
;; ld c, #0x52
;; rst #0x10
;; pop ix
ret ret
__endasm; __endasm;
} }
@@ -460,11 +439,14 @@ uint8_t wherex(void) __naked
{ {
__asm __asm
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE. ;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
push ix ;; push ix
ld c, #0x53 ld c, #0x8e ; BIOS GetCursor
rst #0x10 rst #0x08
pop ix ;; ld c, #0x53
ld a, e ;; rst #0x10
;; pop ix
ld a, e
ld (_pc_place), de
ret ret
__endasm; __endasm;
} }
@@ -472,11 +454,14 @@ uint8_t wherex(void) __naked
uint8_t wherey(void) __naked uint8_t wherey(void) __naked
{ {
__asm __asm
push ix ;; push ix
ld c, #0x53 ld c, #0x8e ; BIOS GetCursor
rst #0x10 rst #0x08
pop ix ;; ld c, #0x53
ld a, d ;; rst #0x10
;; pop ix
ld a, d
ld (_pc_place), de
ret ret
__endasm; __endasm;
} }
@@ -485,10 +470,13 @@ uint16_t wherexy(void) __naked
{ {
__asm __asm
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE. ;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
push ix ;; push ix
ld c, #0x53 ld c, #0x8e ; BIOS GetCursor
rst #0x10 rst #0x08
pop ix ;; ld c, #0x53
;; rst #0x10
;; pop ix
ld (_pc_place), de
ret ret
__endasm; __endasm;
} }
@@ -577,12 +565,12 @@ uint16_t rdchar(uint8_t x, uint8_t y) __naked
extern uint8_t _videomode_raw_get(void); extern uint8_t _videomode_raw_get(void);
extern int _videomode_raw_set(uint8_t mode); extern int _videomode_raw_set(uint8_t mode);
uint8_t get_videotextmode(void) uint8_t gettextmode(void)
{ {
return _videomode_raw_get(); return _videomode_raw_get();
} }
int set_videotextmode(uint8_t mode) int settextmode(uint8_t mode)
{ {
/* Refuse anything that isn't a known text mode — otherwise a stray /* 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. */ * GFX_MODE_* value could swap the screen out from under text I/O. */
@@ -600,18 +588,17 @@ int set_videotextmode(uint8_t mode)
* *
* 0x00..0xFF — real attribute (4-bit FG | 3-bit BG | 1-bit blink) * 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 */ * KEEP_EXIST_ATTR (0xFFFF) — putch/cputs fall back to fast no-attr path */
int16_t g_text_attr = 0x0F;
int16_t set_text_attr(int16_t attr) int16_t set_text_attr(int16_t attr)
{ {
int16_t prev = g_text_attr; int16_t prev = g_text_attr.value;
g_text_attr = attr; g_text_attr.value = attr;
return prev; return prev;
} }
int16_t get_text_attr(void) int16_t get_text_attr(void)
{ {
return g_text_attr; return g_text_attr.value;
} }
/* ---- Turbo-C-style palette helpers -------------------------------- /* ---- Turbo-C-style palette helpers --------------------------------
@@ -620,21 +607,21 @@ int16_t get_text_attr(void)
void textcolor(uint8_t fg) void textcolor(uint8_t fg)
{ {
/* If we were KEEP_EXIST_ATTR, switch to a real attr first. */ fg = (fg & 0x07);
uint8_t cur = ((uint16_t)g_text_attr > 0xFF) ? 0x00 : (uint8_t)g_text_attr; g_text_attr.value = (g_text_attr.byte.low & 0xF0) | fg;
g_text_attr = (int16_t)((cur & 0xF0) | (fg & 0x0F)); // g_text_attr.byte.high = 0;
} }
void textbackground(uint8_t bg) void textbackground(uint8_t bg)
{ {
uint8_t cur = ((uint16_t)g_text_attr > 0xFF) ? 0x00 : (uint8_t)g_text_attr; bg = (bg & 0x07) << 4;
/* Background uses 3 bits (4..6); preserve blink (bit 7) too. */ g_text_attr.value = (g_text_attr.byte.low & 0x0F) | bg;
g_text_attr = (int16_t)((cur & 0x8F) | ((bg & 0x07) << 4)); // g_text_attr.byte.high = 0;
} }
void textattr(uint8_t attr) void textattr(uint8_t attr)
{ {
g_text_attr = (int16_t)attr; g_text_attr.value = (uint16_t)attr;
} }
/* ---- Solid-C compatibility ---------------------------------------- */ /* ---- Solid-C compatibility ---------------------------------------- */
+14 -2
View File
@@ -74,12 +74,24 @@ uint16_t getkey(void);
#define KEY_PGUP 0x59 #define KEY_PGUP 0x59
#define KEY_INS 0x50 /* numpad 0; not verified */ #define KEY_INS 0x50 /* numpad 0; not verified */
#define KEY_DEL 0x55 /* numpad 5/.; not verified */ #define KEY_DEL 0x55 /* numpad 5/.; not verified */
typedef union {
uint16_t value;
struct {
uint8_t low;
uint8_t high;
} byte;
} two_bytes;
char putch (char c); char putch (char c);
char cputs (const char *s); char cputs (const char *s);
int cprintf(const char *fmt, ...); int cprintf(const char *fmt, ...);
void clrscr(void); void clrscr(void);
void gotoxy(uint8_t x, uint8_t y); void gotoxy(uint8_t x, uint8_t y);
/* Solid-C compatibility helpers. */ /* Solid-C compatibility helpers. */
#define home() gotoxy(0, 0) #define home() gotoxy(0, 0)
#define inp(port) z80_inp(port) #define inp(port) z80_inp(port)
@@ -124,8 +136,8 @@ void clrscr_attr(uint8_t attr);
#define TEXT_MODE_40x32 0x02 #define TEXT_MODE_40x32 0x02
#define TEXT_MODE_80x32 0x03 #define TEXT_MODE_80x32 0x03
uint8_t get_videotextmode(void); uint8_t gettextmode(void);
int set_videotextmode(uint8_t mode); /* 0 OK, -1 + errno on bad mode */ int settextmode(uint8_t mode); /* 0 OK, -1 + errno on bad mode */
/* ------------------------------------------------------------------ * /* ------------------------------------------------------------------ *
* Text-output attribute (used by the conio set: putch / cputs / cprintf). * Text-output attribute (used by the conio set: putch / cputs / cprintf).
+27 -5
View File
@@ -1,6 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <conio.h> #include <conio.h>
#include <unistd.h> /* sleep */ #include <unistd.h> /* sleep */
#include <sprinter_exit.h>
/* /*
* Exercises every conio function: * Exercises every conio function:
@@ -16,6 +17,8 @@
* (0,0), (79,0), (0,31), (79,31). * (0,0), (79,0), (0,31), (79,31).
*/ */
extern two_bytes pc_place;
int main(void) int main(void)
{ {
/* 1. clrscr + diagnostic probes + a centered banner. /* 1. clrscr + diagnostic probes + a centered banner.
@@ -27,9 +30,16 @@ int main(void)
*/ */
clrscr(); clrscr();
gotoxy(0, 0); putch('0'); gotoxy(1, 0);
textattr(0x07);
putch('0');
textattr(0x70); textattr(0x70);
gotoxy(1, 0); putch('1'); putch('1');
// gotoxy(10, 20);
printf("X = %u, Y = %u\n", pc_place.byte.low, pc_place.byte.high);
uint8_t x = wherex();
uint8_t y = wherey();
printf("X = %u, Y = %u\n", x, y);
set_putch_raw_mode(1); set_putch_raw_mode(1);
@@ -51,11 +61,23 @@ int main(void)
} }
} }
set_text_attr(0x011D);
set_putch_raw_mode(0); set_putch_raw_mode(0);
gotoxy(0,22); gotoxy(0,22);
set_text_attr(0x000E); cputs("Test message line 1\nTest message line2\rline3\n\r1234\t5678\b90\n\r");
// cputs("Test message line");
cputs("Test message line 1\nTest message line2\rline3\n\r\n\r1234\t5678\b90"); set_putch_raw_mode(1);
gotoxy(0,25);
cputs("Test message line 1\nTest message line2\rline3\n\r1234\t5678\b90\n\r");
set_text_attr(0x001D);
set_putch_raw_mode(0);
gotoxy(0,28);
cputs("Test message line 1\nTest message line2\rline3\n\r1234\t5678\b90\n\r");
set_putch_raw_mode(1);
gotoxy(0,31);
cputs("Test message line 1\nTest message line2\rline3\n\r1234\t5678\b90\n\r");
(void)getch(); (void)getch();