]> git.unchartedbackwaters.co.uk Git - francis/winuae.git/commitdiff
Cycle-accurate Paula serial port emulation.
authorToni Wilen <twilen@winuae.net>
Fri, 16 Sep 2022 17:50:25 +0000 (20:50 +0300)
committerToni Wilen <twilen@winuae.net>
Fri, 16 Sep 2022 17:50:25 +0000 (20:50 +0300)
custom.cpp
events.cpp
include/events.h
include/serial.h
od-win32/parser.cpp
od-win32/parser.h
od-win32/serial_win32.cpp

index 49a482d0c5217cca2caf2825ea04b7d62550e4af..de21e723e368378abe2e03b07298efcca539a8ed 100644 (file)
@@ -7720,7 +7720,7 @@ void rethink_uae_int(void)
 
 static void rethink_intreq(void)
 {
-       serial_check_irq();
+       serial_rethink();
        devices_rethink();
 }
 
index 2ab3fc89820c07914db5c43ddce4a207c0a43bdc..20d661a3c33793ef56f15256485e720856bedf7e 100644 (file)
@@ -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++) {
index 7e9c8c234eeb92e2fd687ba1a0e16262e9248e00..133942242deb3054c68885fff16286cf9893429f 100644 (file)
@@ -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)
 {
index d603969d11ca52220a7071d468baedf764117019..a0d782df88bd037836bc2de9209e3eae48b61b49 100644 (file)
@@ -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*);
index d13568a2d6cf06ccbfb911566ba1b173f7ec66f0..0dcbad8e218a0a3b9fb8df42e7adbcb134fb9056 100644 (file)
@@ -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);
index 36a44fa5cda0297f41425b38a848ebf80abe9ce1..ccf905cc70b3de590562808746608d0b74da32c6 100644 (file)
@@ -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
index 3f24f1f50f326a0910105b0a6e823f436f69a50b..334258baa3c31a932775fe4f7ffb76eb4e29a3b3 100644 (file)
@@ -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)