From 5721bcfc70decc17dbfcca11f62708e79f08463f Mon Sep 17 00:00:00 2001 From: Toni Wilen Date: Fri, 16 Sep 2022 20:50:25 +0300 Subject: [PATCH] Cycle-accurate Paula serial port emulation. --- custom.cpp | 2 +- events.cpp | 16 ++ include/events.h | 1 + include/serial.h | 4 +- od-win32/parser.cpp | 7 + od-win32/parser.h | 1 + od-win32/serial_win32.cpp | 306 +++++++++++++++++++++++++++++--------- 7 files changed, 263 insertions(+), 74 deletions(-) diff --git a/custom.cpp b/custom.cpp index 49a482d0..de21e723 100644 --- a/custom.cpp +++ b/custom.cpp @@ -7720,7 +7720,7 @@ void rethink_uae_int(void) static void rethink_intreq(void) { - serial_check_irq(); + serial_rethink(); devices_rethink(); } diff --git a/events.cpp b/events.cpp index 2ab3fc89..20d661a3 100644 --- a/events.cpp +++ b/events.cpp @@ -399,6 +399,22 @@ void event2_newevent_xx (int no, evt_t t, uae_u32 data, evfunc2 func) MISC_handler(); } +void event2_newevent_x_replace_exists(evt_t t, uae_u32 data, evfunc2 func) +{ + for (int i = 0; i < ev2_max; i++) { + if (eventtab2[i].active && eventtab2[i].handler == func) { + eventtab2[i].active = false; + if (t <= 0) { + func(data); + return; + } + event2_newevent_xx(-1, t * CYCLE_UNIT, data, func); + return; + } + } +} + + void event2_newevent_x_replace(evt_t t, uae_u32 data, evfunc2 func) { for (int i = 0; i < ev2_max; i++) { diff --git a/include/events.h b/include/events.h index 7e9c8c23..13394224 100644 --- a/include/events.h +++ b/include/events.h @@ -133,6 +133,7 @@ STATIC_INLINE bool cycles_in_range(evt_t endcycles) extern void MISC_handler(void); extern void event2_newevent_xx(int no, evt_t t, uae_u32 data, evfunc2 func); extern void event2_newevent_x_replace(evt_t t, uae_u32 data, evfunc2 func); +extern void event2_newevent_x_replace_exists(evt_t t, uae_u32 data, evfunc2 func); STATIC_INLINE void event2_newevent_x(int no, evt_t t, uae_u32 data, evfunc2 func) { diff --git a/include/serial.h b/include/serial.h index d603969d..a0d782df 100644 --- a/include/serial.h +++ b/include/serial.h @@ -32,8 +32,8 @@ extern int doreadser, serstat; extern void serial_flush_buffer (void); -extern void serial_hsynchandler (void); -extern void serial_check_irq (void); +extern void serial_hsynchandler(void); +extern void serial_rethink(void); extern int uaeser_getdatalength (void); extern int uaeser_getbytespending (void*); diff --git a/od-win32/parser.cpp b/od-win32/parser.cpp index d13568a2..0dcbad8e 100644 --- a/od-win32/parser.cpp +++ b/od-win32/parser.cpp @@ -1604,6 +1604,13 @@ int enumserialports (void) comports[cnt]->name = my_strdup (_T("WinUAE inter-process serial port")); cnt++; } + if (cnt < MAX_SERPAR_PORTS) { + comports[cnt] = xcalloc(struct serparportinfo, 1); + comports[cnt]->dev = my_strdup (SERIAL_LOOPBACK); + comports[cnt]->cfgname = my_strdup (comports[cnt]->dev); + comports[cnt]->name = my_strdup (_T("WinUAE loopback serial port")); + cnt++; + } if (cnt < MAX_SERPAR_PORTS) { comports[cnt] = xcalloc(struct serparportinfo, 1); diff --git a/od-win32/parser.h b/od-win32/parser.h index 36a44fa5..ccf905cc 100644 --- a/od-win32/parser.h +++ b/od-win32/parser.h @@ -32,6 +32,7 @@ bool shmem_serial_create(void); int shmem_serial_state(void); #define SERIAL_INTERNAL _T("INTERNAL_SERIAL") +#define SERIAL_LOOPBACK _T("LOOPBACK_SERIAL") #define TIOCM_CAR 1 #define TIOCM_DSR 2 diff --git a/od-win32/serial_win32.cpp b/od-win32/serial_win32.cpp index 3f24f1f5..334258ba 100644 --- a/od-win32/serial_win32.cpp +++ b/od-win32/serial_win32.cpp @@ -52,6 +52,9 @@ static HANDLE sermap_handle; static uae_u8 *sermap_data; static bool sermap_master; static bool sermap_enabled; +static bool serloop_enabled; +static bool serempty_enabled; +static uae_u8 serstatus; #define SER_MEMORY_MAPPING _T("WinUAE_Serial") @@ -164,12 +167,17 @@ bool shmem_serial_create(void) #endif static int data_in_serdat; /* new data written to SERDAT */ +static evt_t data_in_serdat_delay; +static evt_t serper_tx_evt; +static int serper_tx_per, serper_tx_bits, serper_tx_cycles; static int data_in_serdatr; /* new data received */ static int data_in_sershift; /* data transferred from SERDAT to shift register */ static int break_in_serdatr; /* break state */ static int break_delay; static uae_u16 serdatshift; /* serial shift register */ +static int serdatshift_bits; static uae_u16 serdatshift_masked; /* stop bit masked */ +static evt_t serdatshift_start; static int ovrun; static int dtr; static int serial_period_hsyncs, serial_period_hsync_counter; @@ -189,28 +197,32 @@ void serial_open (void); void serial_close (void); uae_u16 serper, serdat, serdatr; +static bool serper_set = false; static const int allowed_baudrates[] = -{ 0, 110, 300, 600, 1200, 2400, 4800, 9600, 14400, -19200, 31400, 38400, 57600, 115200, 128000, 256000, -1 }; +{ + 0, 110, 300, 600, 1200, 2400, 4800, 9600, 14400, + 19200, 31400, 38400, 57600, 115200, 128000, 256000, -1 +}; + +static void sersend_end(uae_u32 v); void SERPER (uae_u16 w) { int baud = 0, i, per; + int oldper = serper; static int warned; - if (serper == w) /* don't set baudrate if it's already ok */ + if (serper == w && serper_set) /* don't set baudrate if it's already ok */ return; + serper_set = true; ninebit = 0; serper = w; if (w & 0x8000) ninebit = 1; w &= 0x7fff; - if (w < 13) - w = 13; - per = w; if (per == 0) per = 1; @@ -221,6 +233,9 @@ void SERPER (uae_u16 w) while (allowed_baudrates[i] >= 0 && per > allowed_baudrates[i] * 100 / 97) i++; baud = allowed_baudrates[i]; + if (baud <= 0) { + baud = allowed_baudrates[1]; + } serial_period_hsyncs = (((serper & 0x7fff) + 1) * (1 + 8 + ninebit + 1 - 1)) / maxhpos; if (serial_period_hsyncs <= 0) @@ -239,7 +254,9 @@ void SERPER (uae_u16 w) serial_period_hsync_counter = 0; - write_log (_T("SERIAL: period=%d, baud=%d, hsyncs=%d, bits=%d, PC=%x\n"), w, baud, serial_period_hsyncs, ninebit ? 9 : 8, M68K_GETPC); + if (!serloop_enabled || seriallog > 0) { + write_log(_T("SERIAL: period=%d, baud=%d, hsyncs=%d, bits=%d, PC=%x\n"), w, baud, serial_period_hsyncs, ninebit ? 9 : 8, M68K_GETPC); + } if (ninebit) baud *= 2; @@ -248,11 +265,53 @@ void SERPER (uae_u16 w) baud = 115200; serial_period_hsyncs = 1; } + serial_recv_previous = -1; serial_send_previous = -1; + #ifdef SERIAL_PORT setbaud (baud); #endif + + // mid transmit period change + if ((serloop_enabled || serempty_enabled) && currprefs.cpu_memory_cycle_exact) { + evt_t c = get_cycles(); + evt_t n = serper_tx_evt + serper_tx_cycles * CYCLE_UNIT; + if (n > c) { + int cycles_transmitted = (int)((c - serper_tx_evt) / CYCLE_UNIT); + serper_tx_cycles -= cycles_transmitted; + if (serper_tx_cycles >= 0) { + int serper_tx_cycles_mod = serper_tx_cycles % serper_tx_per; + serper_tx_cycles /= serper_tx_per; + serper_tx_per = (serper & 0x7fff) + 1; + serper_tx_cycles *= serper_tx_per; + serper_tx_cycles += serper_tx_cycles_mod; + serper_tx_evt = c; + event2_newevent_x_replace_exists(serper_tx_cycles, 0, sersend_end); + } + } + } + +} + +static void serial_rx_irq(void) +{ + // Data in receive buffer + data_in_serdatr = 1; + if (currprefs.cpu_memory_cycle_exact) { + INTREQ_INT(11, 9); + } else { + INTREQ_INT(11, 0); + } +} + +void serial_rethink(void) +{ + if (!currprefs.cpu_memory_cycle_exact) { + if (data_in_serdatr) { + INTREQ_0(0x8000 | (1 << 11)); + } + } } static TCHAR docharlog(int v) @@ -310,9 +369,8 @@ static void checkreceive_enet (void) serdatr |= 0x200; else serdatr |= 0x100; - data_in_serdatr = 1; serdatr_last_got = 0; - serial_check_irq (); + serial_rx_irq(); #if SERIALDEBUG > 2 write_log (_T("SERIAL: received %02X (%c)\n"), serdatr & 0xff, dochar (serdatr)); #endif @@ -450,9 +508,8 @@ static void checkreceive_serial (void) } } - data_in_serdatr = 1; serdatr_last_got = 0; - serial_check_irq(); + serial_rx_irq(); #if SERIALDEBUG > 2 write_log(_T("SERIAL: received %02X (%c)\n"), serdatr & 0xff, dochar(serdatr)); #endif @@ -464,8 +521,12 @@ static void serdatcopy(void); static void checksend(void) { - if (data_in_sershift != 1 && data_in_sershift != 2) + if (data_in_sershift != 1 && data_in_sershift != 2) { + return; + } + if (serloop_enabled) { return; + } #ifdef ARCADIA if (alg_flag || currprefs.genlock_image >= 7) { @@ -475,6 +536,9 @@ static void checksend(void) if (cubo_enabled) { touch_serial_read(serdatshift); } + if (serempty_enabled) { + return; + } #ifdef SERIAL_MAP if (sermap_data && sermap_enabled) shmem_serial_send(serdatshift); @@ -533,6 +597,22 @@ static bool checkshiftempty(void) return false; } +static void sersend_end(uae_u32 v) +{ + data_in_sershift = 0; + serdatcopy(); + data_in_serdat = 0; + data_in_serdat_delay = get_cycles() + CYCLE_UNIT; +} + +static void sersend_serloop(uae_u32 v) +{ + serdatr = serdatshift; + serial_rx_irq(); + event2_newevent_xx(-1, v * CYCLE_UNIT, 0, sersend_end); +} + + static void sersend_ce(uae_u32 v) { if (checkshiftempty()) { @@ -547,15 +627,15 @@ static void serdatcopy(void) { int bits; - if (data_in_sershift || !data_in_serdat) + if (data_in_sershift > 0 || !data_in_serdat) return; serdatshift = serdat; bits = 8; if ((serdatshift & 0xff80) == 0x80) { bits = 7; } + serdatshift_start = get_cycles(); serdatshift_masked = serdatshift & ((1 << bits) - 1); - data_in_sershift = 1; data_in_serdat = 0; if (seriallog > 0 || (consoleopen && seriallog < 0)) { @@ -578,32 +658,58 @@ static void serdatcopy(void) } } + if (data_in_sershift == 0) { + INTREQ_INT(0, 1); + } + data_in_sershift = 1; + + serdatshift_bits = 16; + for (int i = 15; i >= 0; i--) { + if (serdatshift & (1 << i)) + break; + serdatshift_bits--; + } + // if someone uses serial port as some kind of timer.. if (currprefs.cpu_memory_cycle_exact) { - int per; + int sper = (serper & 0x7fff) + 1; + int per = sper * (serdatshift_bits + 1); - bits = 16 + 1; - for (int i = 15; i >= 0; i--) { - if (serdatshift & (1 << i)) - break; - bits--; + serper_tx_evt = get_cycles(); + serper_tx_per = sper; + serper_tx_bits = serdatshift_bits + 1; + serper_tx_cycles = per; + + // not connected, emulate only TX + if (serempty_enabled) { + event2_newevent_xx(-1, per * CYCLE_UNIT, 0, sersend_end); + return; } - // assuming when last bit goes out, transmit buffer - // becomes empty, not when last bit has finished - // transmitting. - per = ((serper & 0x7fff) + 1) * (bits - 1); + + // loopback receive complete + if (serloop_enabled) { + int recper = sper * (ninebit ? 10 : 9) + (sper - 1) / 2; + int perdiff = per - recper; + if (perdiff < 0) { + perdiff = 0; + } + + // TX to RX delay (1488/1489 chip delays) + recper += 3; + + event2_newevent_xx(-1, recper * CYCLE_UNIT, perdiff, sersend_serloop); + return; + } + if (lastbitcycle_active_hsyncs) { // if last bit still transmitting, add remaining time. - int extraper = (int)(lastbitcycle - get_cycles()); - if (extraper > 0) - per += extraper / CYCLE_UNIT; + int extraper = (int)((lastbitcycle - get_cycles()) / CYCLE_UNIT); + per += extraper; } - if (per < 4) - per = 4; + event2_newevent_x_replace(per, 0, sersend_ce); } - INTREQ(0x8000 | 0x0001); checksend(); } @@ -618,9 +724,8 @@ void serial_hsynchandler (void) int ch = ld_serial_write(); if (ch >= 0) { serdatr = ch | 0x100; - data_in_serdatr = 1; serdatr_last_got = 0; - serial_check_irq (); + serial_rx_irq(); } } #endif @@ -628,17 +733,15 @@ void serial_hsynchandler (void) int ch = touch_serial_write(); if (ch >= 0) { serdatr = ch | 0x100; - data_in_serdatr = 1; serdatr_last_got = 0; - serial_check_irq(); + serial_rx_irq(); } } if (seriallog > 1 && !data_in_serdatr && gotlogwrite) { int ch = read_log(); if (ch > 0) { serdatr = ch | 0x100; - data_in_serdatr = 1; - serial_check_irq (); + serial_rx_irq(); } } @@ -649,8 +752,7 @@ void serial_hsynchandler (void) uae_u16 v = shmem_serial_receive(); if (v != 0xffff) { serdatr = v; - data_in_serdatr = 1; - serial_check_irq(); + serial_rx_irq(); } } #endif @@ -677,50 +779,83 @@ void serial_hsynchandler (void) } } -void SERDAT (uae_u16 w) +static void SERDAT_send(uae_u32 v) { + uae_u16 w = (uae_u16)v; #if SERIALDEBUG > 2 write_log(_T("SERIAL: SERDAT write 0x%04x (%c) PC=%x\n"), w, dochar(w), M68K_GETPC); #endif - serdatcopy(); + if (currprefs.cpu_memory_cycle_exact) { + serdat = w; + data_in_serdat = 1; + if (!data_in_sershift) { + if (serloop_enabled || serempty_enabled) { + data_in_sershift = -1; + INTREQ_INT(0, 1); + } + serdatcopy(); + } + + } else { + serdatcopy(); - serdat = w; + serdat = w; - if (!w) { + if (!w) { #if SERIALDEBUG > 1 - write_log (_T("SERIAL: zero serial word written?! PC=%x\n"), M68K_GETPC); + write_log (_T("SERIAL: zero serial word written?! PC=%x\n"), M68K_GETPC); #endif - return; - } + return; + } #if SERIALDEBUG > 1 - if (data_in_serdat) { - write_log (_T("SERIAL: program wrote to SERDAT but old byte wasn't fetched yet\n")); - } + if (data_in_serdat) { + write_log (_T("SERIAL: program wrote to SERDAT but old byte wasn't fetched yet\n")); + } #endif - data_in_serdat = 1; - serdatcopy(); + data_in_serdat = 1; + serdatcopy(); + } } uae_u16 SERDATR(void) { serdatr &= 0x03ff; - if (!data_in_serdat) { - serdatr |= 0x2000; + if (!data_in_serdat && get_cycles() >= data_in_serdat_delay) { + serdatr |= 0x2000; // TBE (Transmit buffer empty) } - if (!data_in_sershift) { - serdatr |= 0x1000; + if (!data_in_sershift && (serdatr & 0x2000)) { + serdatr |= 0x1000; // TSRE (Transmit shift register empty) } if (data_in_serdatr) { - serdatr |= 0x4000; + serdatr |= 0x4000; // RBF (Receive buffer full) } if (ovrun) { - serdatr |= 0x8000; + serdatr |= 0x8000; // OVRUN (Receiver overrun) } - if (break_in_serdatr <= 0) { - serdatr |= 0x0800; + if (serloop_enabled) { + int per = ((serper & 0x7fff) + 1) * CYCLE_UNIT; + evt_t sper = per * serdatshift_bits; + evt_t c = get_cycles(); + evt_t diff = serdatshift_start + sper - c; + if (break_in_serdatr < 0) { + serdatr |= 0x0800; + } else if (diff > 0) { + int bit = (int)(c - serdatshift_start) / per; + if (bit > 0 && !(serdatshift & (1 << (bit - 1)))) { + serdatr |= 0x0800; + } + } else { + if (break_in_serdatr <= 0) { + serdatr |= 0x0800; + } + } + } else { + if (break_in_serdatr <= 0) { + serdatr |= 0x0800; // RXD + } } #if SERIALDEBUG > 2 write_log (_T("SERIAL: read 0x%04x (%c) %x\n"), serdatr, dochar (serdatr), M68K_GETPC); @@ -729,16 +864,18 @@ uae_u16 SERDATR(void) return serdatr; } -void serial_rbf_clear(void) +void SERDAT(uae_u16 w) { - ovrun = 0; + if (currprefs.cpu_memory_cycle_exact) { + event2_newevent_xx(-1, 1 * CYCLE_UNIT, w, SERDAT_send); + } else { + SERDAT_send(w); + } } -void serial_check_irq (void) +void serial_rbf_clear(void) { - // Data in receive buffer - if (data_in_serdatr) - INTREQ_0(0x8000 | 0x0800); + ovrun = 0; } void serial_dtr_on (void) @@ -787,9 +924,19 @@ uae_u8 serial_readstatus (uae_u8 dir) int status = 0; uae_u8 serbits = oldserbits; + if (serloop_enabled) { + if (serstatus & 0x80) { // DTR -> DSR + CD + status |= TIOCM_DSR | TIOCM_CAR; + } + if (serstatus & 0x10) { // RTS -> CTS + status |= TIOCM_CTS; + } + } else { #ifdef SERIAL_PORT - getserstat (&status); + getserstat(&status); #endif + } + if (!(status & TIOCM_CAR)) { if (!(serbits & 0x20)) { serbits |= 0x20; @@ -851,6 +998,8 @@ uae_u8 serial_writestatus (uae_u8 newstate, uae_u8 dir) { static int logcnt = 10; + serstatus = newstate & dir; + #ifdef SERIAL_PORT if (((oldserbits ^ newstate) & 0x80) && (dir & 0x80)) { if (newstate & 0x80) @@ -925,6 +1074,10 @@ void serial_open (void) } else if (!_tcsicmp(currprefs.sername, SERIAL_INTERNAL)) { sermap_enabled = true; #endif + } else if (!_tcsicmp(currprefs.sername, SERIAL_LOOPBACK)) { + serloop_enabled = true; + } else if (!currprefs.sername[0]) { + serempty_enabled = true; } else { if(!openser (currprefs.sername)) { write_log (_T("SERIAL: Could not open device %s\n"), currprefs.sername); @@ -935,27 +1088,32 @@ void serial_open (void) #endif } -void serial_close (void) +void serial_close(void) { #ifdef SERIAL_PORT - closeser (); + closeser(); #ifdef SERIAL_ENET - enet_close (); + enet_close(); #endif serdev = 0; #ifdef SERIAL_MAP sermap_deactivate(); #endif #endif + serloop_enabled = false; + serempty_enabled = false; + serper_set = false; } -void serial_init (void) +void serial_init(void) { #ifdef SERIAL_PORT - if (!currprefs.use_serial) + if (!currprefs.use_serial) { + serial_open(); return; + } if (!currprefs.serial_demand) - serial_open (); + serial_open(); #endif } @@ -966,6 +1124,12 @@ void serial_exit (void) #endif dtr = 0; /* just closes *opened* filehandles which is ok */ oldserbits = 0; /* when exiting. */ + data_in_serdat = 0; + data_in_serdatr = 0; + data_in_sershift = 0; + break_in_serdatr = 0; + ovrun = 0; + serdatr_last_got = 0; } void serial_uartbreak (int v) -- 2.47.3