From 8cecd54410cf050a2aa5ae54e75f5d259d56d639 Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Sat, 24 Oct 2020 11:18:48 -0400 Subject: [PATCH] emul/sms: add PS/2 keyboard emulation --- emul/emul.c | 3 ++ emul/emul.h | 3 ++ emul/hw/sms/Makefile | 2 +- emul/hw/sms/README.md | 5 +++ emul/hw/sms/kbd.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ emul/hw/sms/kbd.h | 16 ++++++++ emul/hw/sms/sms.c | 102 +++++++++++++++++++++++++++++++++++--------------- recipes/sms/kbd.md | 2 +- 8 files changed, 202 insertions(+), 32 deletions(-) create mode 100644 emul/hw/sms/kbd.c create mode 100644 emul/hw/sms/kbd.h diff --git a/emul/emul.c b/emul/emul.c index a2ff6c1..0db2766 100644 --- a/emul/emul.c +++ b/emul/emul.c @@ -193,3 +193,6 @@ void emul_printdebug() fprintf(stderr, "Min SP: %04x\n", m.minsp); fprintf(stderr, "Max IX: %04x\n", m.maxix); } + +byte iord_noop() { return 0; } +void iowr_noop(byte val) {} diff --git a/emul/emul.h b/emul/emul.h index 1374d80..f79bc53 100644 --- a/emul/emul.h +++ b/emul/emul.h @@ -37,3 +37,6 @@ void emul_trace(ushort addr); void emul_memdump(); void emul_debugstr(char *s); void emul_printdebug(); +// use when a port is a NOOP, but it's not an error to access it. +byte iord_noop(); +void iowr_noop(byte val); diff --git a/emul/hw/sms/Makefile b/emul/hw/sms/Makefile index dae8b22..b04cc17 100644 --- a/emul/hw/sms/Makefile +++ b/emul/hw/sms/Makefile @@ -1,5 +1,5 @@ EXTOBJS = ../../emul.o ../../libz80/libz80.o -OBJS = sms.o vdp.o port.o pad.o +OBJS = sms.o vdp.o port.o pad.o kbd.o TARGET = sms CFLAGS += `pkg-config --cflags xcb` LDFLAGS += `pkg-config --libs xcb` diff --git a/emul/hw/sms/README.md b/emul/hw/sms/README.md index f5d1e0c..fb62eb8 100644 --- a/emul/hw/sms/README.md +++ b/emul/hw/sms/README.md @@ -25,4 +25,9 @@ pad are: * K --> C * L --> Start +If your ROM is configured with PS/2 keyboard input, run this emulator with the +`-k` flag to replace SMS pad emulation with keyboard emulation. + +In both cases (pad or keyboard), only port A emulation is supported. + Press ESC to quit. diff --git a/emul/hw/sms/kbd.c b/emul/hw/sms/kbd.c new file mode 100644 index 0000000..c445a3b --- /dev/null +++ b/emul/hw/sms/kbd.c @@ -0,0 +1,101 @@ +#include "kbd.h" + +void kbd_init(Kbd *kbd, Tristate *TH) +{ + kbd->kc = 0; + kbd->breaking = false; + kbd->TH = TH; +} + +void kbd_pressshift(Kbd *kbd, bool ispressed) +{ + kbd->kc = 0x12; // shift keycode + kbd->breaking = !ispressed; +} + +void kbd_presskey(Kbd *kbd, uint8_t key) +{ + kbd->kc = 0; + switch (key) { + case '\t': kbd->kc = 0xd; break; + case '`': kbd->kc = 0xe; break; + case 'q': kbd->kc = 0x15; break; + case '1': kbd->kc = 0x16; break; + case 'z': kbd->kc = 0x1a; break; + case 's': kbd->kc = 0x1b; break; + case 'a': kbd->kc = 0x1c; break; + case 'w': kbd->kc = 0x1d; break; + case '2': kbd->kc = 0x1e; break; + case 'c': kbd->kc = 0x21; break; + case 'x': kbd->kc = 0x22; break; + case 'd': kbd->kc = 0x23; break; + case 'e': kbd->kc = 0x24; break; + case '4': kbd->kc = 0x25; break; + case '3': kbd->kc = 0x26; break; + case ' ': kbd->kc = 0x29; break; + case 'v': kbd->kc = 0x2a; break; + case 'f': kbd->kc = 0x2b; break; + case 't': kbd->kc = 0x2c; break; + case 'r': kbd->kc = 0x2d; break; + case '5': kbd->kc = 0x2e; break; + case 'n': kbd->kc = 0x31; break; + case 'b': kbd->kc = 0x32; break; + case 'h': kbd->kc = 0x33; break; + case 'g': kbd->kc = 0x34; break; + case 'y': kbd->kc = 0x35; break; + case '6': kbd->kc = 0x36; break; + case 'm': kbd->kc = 0x3a; break; + case 'j': kbd->kc = 0x3b; break; + case 'u': kbd->kc = 0x3c; break; + case '7': kbd->kc = 0x3d; break; + case '8': kbd->kc = 0x3e; break; + case ',': kbd->kc = 0x41; break; + case 'k': kbd->kc = 0x42; break; + case 'i': kbd->kc = 0x43; break; + case 'o': kbd->kc = 0x44; break; + case '0': kbd->kc = 0x45; break; + case '9': kbd->kc = 0x46; break; + case '.': kbd->kc = 0x49; break; + case '/': kbd->kc = 0x4a; break; + case 'l': kbd->kc = 0x4b; break; + case ';': kbd->kc = 0x4c; break; + case 'p': kbd->kc = 0x4d; break; + case '-': kbd->kc = 0x4e; break; + case '\'': kbd->kc = 0x52; break; + case '[': kbd->kc = 0x54; break; + case '=': kbd->kc = 0x55; break; + case '\r': kbd->kc = 0x5a; break; + case ']': kbd->kc = 0x5b; break; + case '\\': kbd->kc = 0x5d; break; + case '\b': kbd->kc = 0x66; break; + } +} + +uint8_t kbd_rd(Kbd *kbd) +{ + // There are 3 modes for reading the kbd: + // 1. TH = highz: we're polling the the existence of a KC + // 2. TH = low: return low nibble + // 3. TH = high: return high nibble and reset KC + uint8_t res = 0; + uint8_t tosend = kbd->kc; + if (kbd->breaking) { + tosend = 0xf0; + } + if (*kbd->TH == TRI_HIGHZ) { // polling + if (!kbd->kc) { + res = 0x10; // indicate KC absence; + } + } else if (*kbd->TH == TRI_LOW) { // TH selected + res = tosend & 0xf; + } else { + res = tosend >> 4; + if (kbd->breaking) { + kbd->breaking = false; + } else { + kbd->kc = 0; + } + } + return res; +} + diff --git a/emul/hw/sms/kbd.h b/emul/hw/sms/kbd.h new file mode 100644 index 0000000..2188111 --- /dev/null +++ b/emul/hw/sms/kbd.h @@ -0,0 +1,16 @@ +#include +#include +#include "port.h" + +#define KBD_BUFSZ 0x10 + +typedef struct { + uint8_t kc; // last keycode to be pressed. 0 means none. + bool breaking; // whether we should send 0xf0 before kc + Tristate *TH; +} Kbd; + +void kbd_init(Kbd *kbd, Tristate *TH); +void kbd_pressshift(Kbd *kbd, bool ispressed); +void kbd_presskey(Kbd *kbd, uint8_t keycode); +uint8_t kbd_rd(Kbd *kbd); diff --git a/emul/hw/sms/sms.c b/emul/hw/sms/sms.c index adc9d18..dcf3ac7 100644 --- a/emul/hw/sms/sms.c +++ b/emul/hw/sms/sms.c @@ -11,6 +11,7 @@ #include "vdp.h" #include "port.h" #include "pad.h" +#include "kbd.h" #define RAMSTART 0xc000 #define VDP_CMD_PORT 0xbf @@ -36,6 +37,8 @@ static VDP vdp; static bool vdp_changed; static Ports ports; static Pad pad; +static Kbd kbd; +static bool use_kbd = false; static uint8_t iord_vdp_cmd() { @@ -62,6 +65,11 @@ static uint8_t iord_pad() return pad_rd(&pad); } +static uint8_t iord_kbd() +{ + return kbd_rd(&kbd); +} + static void iowr_vdp_cmd(uint8_t val) { vdp_cmd_wr(&vdp, val); @@ -159,38 +167,49 @@ void draw_pixels() static bool _handle_keypress(xcb_generic_event_t *e) { xcb_key_press_event_t *ev = (xcb_key_press_event_t *)e; + if (ev->detail == 0x09) { // ESC + return true; + } bool ispressed = e->response_type == XCB_KEY_PRESS; // change keycode into symbol xcb_get_keyboard_mapping_reply_t* km = xcb_get_keyboard_mapping_reply( conn, xcb_get_keyboard_mapping(conn, ev->detail, 1), NULL); if (km->length) { xcb_keysym_t* keysyms = (xcb_keysym_t*)(km + 1); - switch (keysyms[0]) { - case XK_Escape: free(km); return true; - case 'w': - pad_setbtn(&pad, PAD_BTN_UP, ispressed); - break; - case 'a': - pad_setbtn(&pad, PAD_BTN_LEFT, ispressed); - break; - case 's': - pad_setbtn(&pad, PAD_BTN_DOWN, ispressed); - break; - case 'd': - pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed); - break; - case 'h': - pad_setbtn(&pad, PAD_BTN_A, ispressed); - break; - case 'j': - pad_setbtn(&pad, PAD_BTN_B, ispressed); - break; - case 'k': - pad_setbtn(&pad, PAD_BTN_C, ispressed); - break; - case 'l': - pad_setbtn(&pad, PAD_BTN_START, ispressed); - break; + if (use_kbd) { + if ((keysyms[0] == XK_Shift_L) || (keysyms[0] == XK_Shift_R)) { + kbd_pressshift(&kbd, ispressed); + } else if (ispressed) { + fprintf(stderr, "pressing %x\n", keysyms[0]); + kbd_presskey(&kbd, keysyms[0]); + } + } else { // pad + switch (keysyms[0]) { + case 'w': + pad_setbtn(&pad, PAD_BTN_UP, ispressed); + break; + case 'a': + pad_setbtn(&pad, PAD_BTN_LEFT, ispressed); + break; + case 's': + pad_setbtn(&pad, PAD_BTN_DOWN, ispressed); + break; + case 'd': + pad_setbtn(&pad, PAD_BTN_RIGHT, ispressed); + break; + case 'h': + pad_setbtn(&pad, PAD_BTN_A, ispressed); + break; + case 'j': + pad_setbtn(&pad, PAD_BTN_B, ispressed); + break; + case 'k': + pad_setbtn(&pad, PAD_BTN_C, ispressed); + break; + case 'l': + pad_setbtn(&pad, PAD_BTN_START, ispressed); + break; + } } } free(km); @@ -242,13 +261,30 @@ void event_loop() } } +static void usage() +{ + fprintf(stderr, "Usage: ./sms [-k] /path/to/rom\n"); +} + int main(int argc, char *argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: ./sms /path/to/rom\n"); + if (argc < 2) { + usage(); + return 1; + } + int ch; + while ((ch = getopt(argc, argv, "k")) != -1) { + switch (ch) { + case 'k': + use_kbd = true; + break; + } + } + if (optind != argc-1) { + usage(); return 1; } - FILE *fp = fopen(argv[1], "r"); + FILE *fp = fopen(argv[optind], "r"); if (fp == NULL) { fprintf(stderr, "Can't open %s\n", argv[1]); return 1; @@ -268,12 +304,18 @@ int main(int argc, char *argv[]) vdp_init(&vdp); vdp_changed = false; ports_init(&ports); - ports.portA_rd = iord_pad; pad_init(&pad, &ports.THA); + kbd_init(&kbd, &ports.THA); + if (use_kbd) { + ports.portA_rd = iord_kbd; + } else { + ports.portA_rd = iord_pad; + } m->iord[VDP_CMD_PORT] = iord_vdp_cmd; m->iord[VDP_DATA_PORT] = iord_vdp_data; m->iord[PORTS_IO1_PORT] = iord_ports_io1; m->iord[PORTS_IO2_PORT] = iord_ports_io2; + m->iord[PORTS_CTL_PORT] = iord_noop; m->iowr[VDP_CMD_PORT] = iowr_vdp_cmd; m->iowr[VDP_DATA_PORT] = iowr_vdp_data; m->iowr[PORTS_CTL_PORT] = iowr_ports_ctl; diff --git a/recipes/sms/kbd.md b/recipes/sms/kbd.md index 6d44a42..89e9894 100644 --- a/recipes/sms/kbd.md +++ b/recipes/sms/kbd.md @@ -105,7 +105,7 @@ to TH (and also the A/B on the '157). Q is hooked to PB0 and TL. We start with the base recipe and add a few things: 1. at the top: `SYSVARS 0x72 + CONSTANT PS2_MEM` -2. After VDP load: `641 LOAD : (ps2kc) (ps2kcB) ;` (that binds us to port B) +2. After VDP load: `621 LOAD : (ps2kc) (ps2kcB) ;` (that binds us to port B) 3. Right after: `411 414 LOADR` (that gives us `(key)`) 4. After `VDP$`: `PS2$`.