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
+136 -149
View File
@@ -15,10 +15,18 @@
#include <stdint.h>
#include <errno.h>
/* Forward extern — definition is further down (after putch/cputs which
* 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
{
__asm
@@ -91,10 +99,6 @@ uint16_t getkey(void) __naked
* 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):
* 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
* g_text_attr is KEEP_EXIST_ATTR, ESTEX's own PUTCHAR/PCHARS handle
* 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; }
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
{
__asm
push ix
ld c, #0x53 ; ESTEX CURSOR
rst #0x10
ld a, d
ld (_pc_row), a
ld a, e
ld (_pc_col), a
pop ix
; push ix
ld c, #0x8e ; BIOS GetCursor
rst #0x08
ld (_pc_place), de
; pop ix
ret
__endasm;
}
@@ -131,14 +131,11 @@ static void _get_cursor(void) __naked
static void _set_cursor(void) __naked
{
__asm
push ix
ld a, (_pc_row)
ld d, a
ld a, (_pc_col)
ld e, a
ld c, #0x52 ; ESTEX LOCATE
rst #0x10
pop ix
; push ix
ld de, (_pc_place)
ld c, #0x84 ; BIOS SetCursor
rst #0x08
; pop ix
ret
__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
* 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 _raw_putch_raw0(char ch, uint8_t attr) __naked
static void _putch_wrchar(char ch, uint8_t attr) __naked
{
(void)ch; (void)attr;
__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
;; 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
@@ -201,18 +204,21 @@ static void _raw_putch_raw0(char ch, uint8_t attr) __naked
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_row)
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_col)
ld a, (_pc_place)
cp #80
ret nc ; off-screen right silently skip
ret nc ; off-screen right silently skip
ld e, a ; E = col
inc a
ld (_pc_col), a ; pc_col++
ld (_pc_place), a ; pc_col++
_rp0_wr:
ld b, l ; B = attr
ld a, c ; A = ch
push ix
@@ -222,56 +228,61 @@ static void _raw_putch_raw0(char ch, uint8_t attr) __naked
ret
_rp0_bs:
ld a, (_pc_col)
ld a, (_pc_place)
or a, a
ret z ; already at col 0 no change
dec a
ld (_pc_col), a
ret
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_col)
and #0xF8 ; floor to mult of 8
add a, #8 ; next mult of 8
cp #80
jr c, _rp0_tab_store
ld a, #80 ; cap at off-screen right
_rp0_tab_store:
ld (_pc_col), a
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_row)
ld a, (_pc_place + 1)
cp #32
ret nc ; already at bottom edge
inc a
ld (_pc_row), a
ld (_pc_place + 1), a
ret
_rp0_cr:
xor a, a
ld (_pc_col), a
ld (_pc_place), a
ret
__endasm;
}
/* 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;
__asm
;; __sdcccall(1): ch in A, attr in L.
ld c, a ; C = ch (save)
ld a, (_pc_row)
ld a, (_pc_place + 1)
cp #32
ret nc ; off-screen bottom silently skip
ld d, a ; D = row
ld a, (_pc_col)
ld a, (_pc_place)
cp #80
ret nc ; off-screen right silently skip
ret nc ; off-screen right silently skip
ld e, a ; E = col
inc a
ld (_pc_col), a ; pc_col++
ld (_pc_place), a ; pc_col++
ld b, l ; B = attr
ld a, c ; A = ch
@@ -283,18 +294,6 @@ static void _raw_putch_raw1(char ch, uint8_t attr) __naked
__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 --------------------------------------- *
*
@@ -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
* single pair per operation. */
char putch(char ch) __naked
{
(void)ch;
@@ -313,38 +313,26 @@ char putch(char ch) __naked
;; A = ch on entry; char return A.
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 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_raw1
call __raw_putch_raw0
jr nz, _putch_use_raw
call __putch_wrchar
jr _putch_after_raw
_putch_use_raw1:
call __raw_putch_raw1
_putch_use_raw:
call __putch_wrchar_raw
_putch_after_raw:
call __set_cursor
ld a, (_pc_ch) ; return value
ret
_putch_fast:
ld a, (_pc_ch)
call __bios_putchar ; __bios_putchar keeps AF
ret
__endasm;
}
char cputs(const char *s) __naked
{
(void)s;
@@ -356,60 +344,48 @@ char cputs(const char *s) __naked
or a, l
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
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
;; --- 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)
or a, a
jr z, _cputs_use_raw0
ld ix, #__raw_putch_raw1
jr _cputs_loop
_cputs_use_raw0:
ld ix, #__raw_putch_raw0
jr nz, _cputs_bios
_cputs_loop:
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
push hl
pop de
jr _cputs_loop
_cputs_loop_end:
_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 caller's IX
xor a, a ; return 0
ret
_cputs_fast:
call __cputs_pchars
pop ix ; restore caller's IX
pop ix ; restore callers IX
xor a, a ; return 0
ret
__endasm;
@@ -446,12 +422,15 @@ void gotoxy(uint8_t x, uint8_t y) __naked
__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 c, #0x52
rst #0x10
pop ix
;; 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;
}
@@ -460,11 +439,14 @@ uint8_t wherex(void) __naked
{
__asm
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
push ix
ld c, #0x53
rst #0x10
pop ix
ld a, e
;; 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;
}
@@ -472,11 +454,14 @@ uint8_t wherex(void) __naked
uint8_t wherey(void) __naked
{
__asm
push ix
ld c, #0x53
rst #0x10
pop ix
ld a, d
;; 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;
}
@@ -485,10 +470,13 @@ uint16_t wherexy(void) __naked
{
__asm
;; ESTEX CURSOR ($53): D = row, E = col. Return col in DE.
push ix
ld c, #0x53
rst #0x10
pop ix
;; push ix
ld c, #0x8e ; BIOS GetCursor
rst #0x08
;; ld c, #0x53
;; rst #0x10
;; pop ix
ld (_pc_place), de
ret
__endasm;
}
@@ -577,12 +565,12 @@ uint16_t rdchar(uint8_t x, uint8_t y) __naked
extern uint8_t _videomode_raw_get(void);
extern int _videomode_raw_set(uint8_t mode);
uint8_t get_videotextmode(void)
uint8_t gettextmode(void)
{
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
* 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)
* 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 prev = g_text_attr;
g_text_attr = 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;
return g_text_attr.value;
}
/* ---- Turbo-C-style palette helpers --------------------------------
@@ -620,21 +607,21 @@ int16_t get_text_attr(void)
void textcolor(uint8_t fg)
{
/* If we were KEEP_EXIST_ATTR, switch to a real attr first. */
uint8_t cur = ((uint16_t)g_text_attr > 0xFF) ? 0x00 : (uint8_t)g_text_attr;
g_text_attr = (int16_t)((cur & 0xF0) | (fg & 0x0F));
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)
{
uint8_t cur = ((uint16_t)g_text_attr > 0xFF) ? 0x00 : (uint8_t)g_text_attr;
/* Background uses 3 bits (4..6); preserve blink (bit 7) too. */
g_text_attr = (int16_t)((cur & 0x8F) | ((bg & 0x07) << 4));
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 = (int16_t)attr;
g_text_attr.value = (uint16_t)attr;
}
/* ---- Solid-C compatibility ---------------------------------------- */
+14 -2
View File
@@ -74,12 +74,24 @@ uint16_t getkey(void);
#define KEY_PGUP 0x59
#define KEY_INS 0x50 /* numpad 0; 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 cputs (const char *s);
int cprintf(const char *fmt, ...);
void clrscr(void);
void gotoxy(uint8_t x, uint8_t y);
/* Solid-C compatibility helpers. */
#define home() gotoxy(0, 0)
#define inp(port) z80_inp(port)
@@ -124,8 +136,8 @@ void clrscr_attr(uint8_t attr);
#define TEXT_MODE_40x32 0x02
#define TEXT_MODE_80x32 0x03
uint8_t get_videotextmode(void);
int set_videotextmode(uint8_t mode); /* 0 OK, -1 + errno on bad mode */
uint8_t gettextmode(void);
int settextmode(uint8_t mode); /* 0 OK, -1 + errno on bad mode */
/* ------------------------------------------------------------------ *
* Text-output attribute (used by the conio set: putch / cputs / cprintf).